diff --git a/codec/opusparser/opusparser.go b/codec/opusparser/opusparser.go new file mode 100644 index 0000000..665a290 --- /dev/null +++ b/codec/opusparser/opusparser.go @@ -0,0 +1,124 @@ +package opusparser + +import ( + "errors" + "time" + + "github.com/deepch/vdk/av" +) + +type CodecData struct { + Channels int +} + +func NewCodecData(channels int) *CodecData { + return &CodecData{Channels: channels} +} + +func (d CodecData) Type() av.CodecType { + return av.OPUS +} + +func (d CodecData) SampleRate() int { + return 48000 +} + +func (d CodecData) ChannelLayout() av.ChannelLayout { + switch d.Channels { + case 1: + return av.CH_MONO + case 2: + return av.CH_STEREO + default: + panic("not implemented") + } +} + +func (d CodecData) SampleFormat() av.SampleFormat { + return av.S16 +} + +func (d CodecData) PacketDuration(pkt []byte) (time.Duration, error) { + return PacketDuration(pkt) +} + +func Channels(pkt []byte) int { + if len(pkt) > 0 && (pkt[0]&0x4) == 0 { + return 1 + } + return 2 +} + +func PacketDuration(pkt []byte) (time.Duration, error) { + if len(pkt) < 1 { + return 0, errors.New("empty opus packet") + } + toc := pkt[0] + config := toc >> 3 + //stereo := (toc & 0x4) != 0 + code := toc & 0x3 + numFr := 0 + switch code { + case 0: + // one frame + if len(pkt) > 1 { + numFr = 1 + } + case 1, 2: + // two frames + if len(pkt) > 2 { + numFr = 2 + } + case 3: + // N frames + if len(pkt) < 2 { + return 0, errors.New("invalid opus packet") + } + numFr = int(pkt[1] & 0x3f) + } + return time.Duration(numFr) * opusFrameTimes[config], nil +} + +var opusFrameTimes = []time.Duration{ + // SILK NB + 10 * time.Millisecond, + 20 * time.Millisecond, + 40 * time.Millisecond, + 60 * time.Millisecond, + // SILK MB + 10 * time.Millisecond, + 20 * time.Millisecond, + 40 * time.Millisecond, + 60 * time.Millisecond, + // SILK WB + 10 * time.Millisecond, + 20 * time.Millisecond, + 40 * time.Millisecond, + 60 * time.Millisecond, + // Hybrid SWB + 10 * time.Millisecond, + 20 * time.Millisecond, + // Hybrid FB + 10 * time.Millisecond, + 20 * time.Millisecond, + // CELT NB + 2500 * time.Microsecond, + 5 * time.Millisecond, + 10 * time.Millisecond, + 20 * time.Millisecond, + // CELT WB + 2500 * time.Microsecond, + 5 * time.Millisecond, + 10 * time.Millisecond, + 20 * time.Millisecond, + // CELT SWB + 2500 * time.Microsecond, + 5 * time.Millisecond, + 10 * time.Millisecond, + 20 * time.Millisecond, + // CELT FB + 2500 * time.Microsecond, + 5 * time.Millisecond, + 10 * time.Millisecond, + 20 * time.Millisecond, +} diff --git a/format/fmp4/esio/builder.go b/format/fmp4/esio/builder.go new file mode 100644 index 0000000..e856071 --- /dev/null +++ b/format/fmp4/esio/builder.go @@ -0,0 +1,98 @@ +package esio + +type builder struct { + buf []byte +} + +func (b *builder) Bytes() []byte { + return b.buf +} + +// Grow the buffer by n bytes and return a slice holding the new area. +// The slice is only valid until the next method called on the builder. +func (b *builder) Grow(n int) []byte { + pos := len(b.buf) + b.buf = append(b.buf, make([]byte, n)...) + return b.buf[pos:] +} + +// WriteByte appends a uint8 +func (b *builder) WriteByte(v byte) error { + b.buf = append(b.buf, v) + return nil +} + +// WriteU16 appends a 16-but unsigned big-endian integer +func (b *builder) WriteU16(v uint16) { + b.buf = append(b.buf, uint8(v>>8), uint8(v)) +} + +// WriteU24 appends a 24-bit unsigned big-endian integer +func (b *builder) WriteU24(v uint32) { + b.buf = append(b.buf, uint8(v>>16), uint8(v>>8), uint8(v)) +} + +// WriteU32 appends a 32-bit unsigned big-endian integer +func (b *builder) WriteU32(v uint32) { + b.buf = append(b.buf, uint8(v>>24), uint8(v>>16), uint8(v>>8), uint8(v)) +} + +// WriteU64 appends a 64-bit unsigned big-endian integer +func (b *builder) WriteU64(v uint64) { + b.buf = append(b.buf, + uint8(v>>56), + uint8(v>>48), + uint8(v>>40), + uint8(v>>32), + uint8(v>>24), + uint8(v>>16), + uint8(v>>8), + uint8(v), + ) +} + +// Write appends a slice. It never returns an error, but implements io.Writer +func (b *builder) Write(d []byte) (int, error) { + b.buf = append(b.buf, d...) + return len(d), nil +} + +// Cursor allocates length bytes and returns a pointer that can be used to access the allocated region later, even after the buffer has grown +func (b *builder) Cursor(length int) cursor { + c := cursor{builder: b, i: len(b.buf)} + b.Grow(length) + c.j = len(b.buf) + return c +} + +// Descriptor writes a descriptor tag and leaves room for a length later. +// Call DescriptorDone on the returned cursor to complete it. +func (b *builder) Descriptor(tag Tag) cursor { + b.WriteByte(byte(tag)) + return b.Cursor(4) +} + +type cursor struct { + builder *builder + i, j int +} + +func (c cursor) Bytes() []byte { + return c.builder.buf[c.i:c.j] +} + +// DescriptorDone completes a descriptor tag by writing the length of its contents. +// Either pass the length of the contents, or -1 if the current end of the buffer is the end of the contents. +func (c cursor) DescriptorDone(length int) { + if length < 0 { + length = len(c.builder.buf) - c.j + } + buf := c.Bytes() + for i := 3; i >= 0; i-- { + v := byte(length >> uint(7*i) & 0x7f) + if i != 0 { + v |= 0x80 + } + buf[3-i] = v + } +} diff --git a/format/fmp4/esio/decoderconf.go b/format/fmp4/esio/decoderconf.go new file mode 100644 index 0000000..be4d8dd --- /dev/null +++ b/format/fmp4/esio/decoderconf.go @@ -0,0 +1,95 @@ +package esio + +import ( + "errors" + "fmt" + + "github.com/deepch/vdk/av" + "github.com/deepch/vdk/codec/aacparser" + "github.com/deepch/vdk/utils/bits/pio" +) + +type DecoderConfigDescriptor struct { + ObjectType ObjectType + StreamType StreamType + BufferSize uint32 + MaxBitrate uint32 + AvgBitrate uint32 + + AudioSpecific []byte +} + +type ObjectType uint8 + +// ISO/IEC 14496-1 7.2.6.6.2 Table 5 +const ObjectTypeAudio = ObjectType(0x40) + +type StreamType uint8 + +// ISO/IEC 14496-1 7.2.6.6.2 Table 6 +const StreamTypeAudioStream = StreamType(0x05) + +func parseDecoderConfig(d []byte) (*DecoderConfigDescriptor, error) { + if len(d) < 13 { + return nil, errors.New("DecoderConfigDescriptor short") + } + conf := &DecoderConfigDescriptor{ + ObjectType: ObjectType(d[0]), + StreamType: StreamType(d[1] >> 2), + BufferSize: pio.U24BE(d[2:]), + MaxBitrate: pio.U32BE(d[5:]), + AvgBitrate: pio.U32BE(d[9:]), + } + d = d[13:] + for len(d) > 0 { + tag, contents, remainder, err := parseHeader(d) + if err != nil { + return nil, fmt.Errorf("DecoderConfigDescriptor: %w", err) + } + d = remainder + switch tag { + case TagDecoderSpecificInfo: + switch conf.ObjectType { + case ObjectTypeAudio: + conf.AudioSpecific = contents + } + } + } + return conf, nil +} + +func (c *DecoderConfigDescriptor) appendTo(b *builder) error { + if c == nil { + return nil + } + cursor := b.Descriptor(TagDecoderConfigDescriptor) + defer cursor.DescriptorDone(-1) + b.WriteByte(byte(c.ObjectType)) + b.WriteByte(byte(c.StreamType<<2) | 1) + b.WriteU24(c.BufferSize) + b.WriteU32(c.MaxBitrate) + b.WriteU32(c.AvgBitrate) + switch { + case c.AudioSpecific != nil: + // ISO/IEC 14496-3 + // 1.6.2.1 - base AudioSpecificConfig + // 4.4.1 - GASpecificConfig + // but we don't actually need to inspect this right now so just preserve the bytes + c2 := b.Descriptor(TagDecoderSpecificInfo) + b.Write(c.AudioSpecific) + c2.DescriptorDone(-1) + } + return nil +} + +func DecoderConfigFromCodecData(stream av.CodecData) (*DecoderConfigDescriptor, error) { + switch cd := stream.(type) { + case aacparser.CodecData: + return &DecoderConfigDescriptor{ + ObjectType: ObjectTypeAudio, + StreamType: StreamTypeAudioStream, + AudioSpecific: cd.MPEG4AudioConfigBytes(), + }, nil + } + return nil, fmt.Errorf("can't marshal %T to DecoderConfigDescriptor", stream) +} diff --git a/format/fmp4/esio/esio.go b/format/fmp4/esio/esio.go new file mode 100644 index 0000000..ce24e51 --- /dev/null +++ b/format/fmp4/esio/esio.go @@ -0,0 +1,162 @@ +package esio + +import ( + "errors" + "fmt" + + "github.com/deepch/vdk/utils/bits/pio" +) + +type StreamDescriptor struct { + ESID uint16 + DependsOn *uint16 + URL *string + OCR *uint16 + + DecoderConfig *DecoderConfigDescriptor + SLConfig *SLConfigDescriptor +} + +// Tag identifies element stream descriptor types +type Tag uint8 + +// ISO/IEC 14496-1:2004 7.2.2 Table 1 +const ( + TagForbidden = Tag(iota) + TagObjectDescriptor + TagInitialObjectDescriptor + TagESDescriptor + TagDecoderConfigDescriptor + TagDecoderSpecificInfo + TagSLConfigDescriptor +) + +const ( + esFlagStreamDependence = 0x80 + esFlagURL = 0x40 + esFlagOCR = 0x20 +) + +func ParseStreamDescriptor(start []byte) (desc *StreamDescriptor, remainder []byte, err error) { + // ISO/IEC 14496-1:2004 7.2.6.5.1 + tag, d, remainder, err := parseHeader(start) + if err != nil { + err = fmt.Errorf("ES_Descriptor: %w", err) + return + } else if tag != TagESDescriptor { + err = fmt.Errorf("expected ES_Descriptor but got tag %02X", tag) + return + } + desc = &StreamDescriptor{ESID: pio.U16BE(d)} + flags := d[2] + d = d[3:] + if flags&esFlagStreamDependence != 0 { + v := pio.U16BE(d) + desc.DependsOn = &v + d = d[2:] + } + if flags&esFlagURL != 0 { + urlLength := d[0] + v := string(d[1 : 1+urlLength]) + desc.URL = &v + d = d[1+urlLength:] + } + if flags&esFlagOCR != 0 { + v := pio.U16BE(d) + desc.OCR = &v + d = d[2:] + } + for len(d) > 0 { + var child []byte + tag, child, d, err = parseHeader(d) + if err != nil { + err = fmt.Errorf("ES_Descriptor: %w", err) + return + } + switch tag { + case TagDecoderConfigDescriptor: + desc.DecoderConfig, err = parseDecoderConfig(child) + case TagSLConfigDescriptor: + desc.SLConfig, err = parseSLConfig(child) + } + if err != nil { + return + } + } + remainder = d + return +} + +func (s *StreamDescriptor) Marshal() ([]byte, error) { + var b builder + cursor := b.Descriptor(TagESDescriptor) + b.WriteU16(s.ESID) + var flags uint8 + if s.DependsOn != nil { + flags |= esFlagStreamDependence + } + if s.URL != nil { + flags |= esFlagURL + } + if s.OCR != nil { + flags |= esFlagOCR + } + b.WriteByte(flags) + if s.DependsOn != nil { + b.WriteU16(*s.DependsOn) + } + if s.URL != nil { + b.WriteByte(byte(len(*s.URL))) + b.Write([]byte(*s.URL)) + } + if s.OCR != nil { + b.WriteU16(*s.OCR) + } + if err := s.DecoderConfig.appendTo(&b); err != nil { + return nil, err + } + if err := s.SLConfig.appendTo(&b); err != nil { + return nil, err + } + cursor.DescriptorDone(-1) + return b.Bytes(), nil +} + +func parseLength(start []byte) (length int, d []byte, err error) { + // ISO/IEC 14496-1:2004 8.3.3 + d = start + for i := 0; i < 4; i++ { + if len(d) == 0 { + err = errors.New("short tag") + return + } + v := d[0] + d = d[1:] + length <<= 7 + length |= int(v & 0x7f) + if v&0x80 == 0 { + break + } + } + return +} + +func parseHeader(start []byte) (tag Tag, contents, d []byte, err error) { + d = start + if len(d) < 2 { + err = errors.New("short tag") + return + } + tag = Tag(d[0]) + length, d, err := parseLength(d[1:]) + if err != nil { + return + } + if length > len(d) { + err = fmt.Errorf("short tag: %02x: expected %d bytes but only got %d", tag, length, len(d)) + return + } + contents = d[:length] + d = d[length:] + return +} diff --git a/format/fmp4/esio/slconf.go b/format/fmp4/esio/slconf.go new file mode 100644 index 0000000..b722e82 --- /dev/null +++ b/format/fmp4/esio/slconf.go @@ -0,0 +1,43 @@ +package esio + +import "errors" + +type SLConfigDescriptor struct { + Predefined SLConfigPredefined + Custom []byte +} + +// SLConfigPredefined references a standard SL config by index +type SLConfigPredefined uint8 + +// ISO/IEC 14496-1:2004 7.3.2.3.2 Table 12 +const ( + SLConfigCustom = SLConfigPredefined(iota) + SLConfigNull + SLConfigMP4 +) + +func parseSLConfig(d []byte) (*SLConfigDescriptor, error) { + // ISO/IEC 14496-1:2004 7.3.2.3 + if len(d) == 0 { + return nil, errors.New("SLConfigDescriptor short") + } + sl := &SLConfigDescriptor{Predefined: SLConfigPredefined(d[0])} + if sl.Predefined == SLConfigCustom { + sl.Custom = d[1:] + } + return sl, nil +} + +func (c *SLConfigDescriptor) appendTo(b *builder) error { + if c == nil { + return nil + } + cursor := b.Descriptor(TagSLConfigDescriptor) + defer cursor.DescriptorDone(-1) + b.WriteByte(byte(c.Predefined)) + if c.Predefined == SLConfigCustom { + b.Write(c.Custom) + } + return nil +} diff --git a/format/fmp4/fmp4io/atom.go b/format/fmp4/fmp4io/atom.go new file mode 100644 index 0000000..63d897d --- /dev/null +++ b/format/fmp4/fmp4io/atom.go @@ -0,0 +1,204 @@ +package fmp4io + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/deepch/vdk/utils/bits/pio" +) + +type Tag uint32 + +func (a Tag) String() string { + var b [4]byte + pio.PutU32BE(b[:], uint32(a)) + for i := 0; i < 4; i++ { + if b[i] == 0 { + b[i] = ' ' + } + } + return string(b[:]) +} + +type Atom interface { + Pos() (int, int) + Tag() Tag + Marshal([]byte) int + Unmarshal([]byte, int) (int, error) + Len() int + Children() []Atom +} + +type AtomPos struct { + Offset int + Size int +} + +func (a AtomPos) Pos() (int, int) { + return a.Offset, a.Size +} + +func (a *AtomPos) setPos(offset int, size int) { + a.Offset, a.Size = offset, size +} + +type Dummy struct { + Data []byte + Tag_ Tag + AtomPos +} + +func (a Dummy) Children() []Atom { + return nil +} + +func (a Dummy) Tag() Tag { + return a.Tag_ +} + +func (a Dummy) Len() int { + return len(a.Data) +} + +func (a Dummy) Marshal(b []byte) int { + copy(b, a.Data) + return len(a.Data) +} + +func (a *Dummy) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + a.Data = b + n = len(b) + return +} + +type FullAtom struct { + Version uint8 + Flags uint32 + AtomPos +} + +func (f FullAtom) marshalAtom(b []byte, tag Tag) (n int) { + pio.PutU32BE(b[4:], uint32(tag)) + pio.PutU8(b[8:], f.Version) + pio.PutU24BE(b[9:], f.Flags) + return 12 +} + +func (f FullAtom) atomLen() int { + return 12 +} + +func (f *FullAtom) unmarshalAtom(b []byte, offset int) (n int, err error) { + f.AtomPos.setPos(offset, len(b)) + n = 8 + if len(b) < n+4 { + return 0, parseErr("fullAtom", offset, nil) + } + f.Version = pio.U8(b[n:]) + f.Flags = pio.U24BE(b[n+1:]) + n += 4 + return +} + +func StringToTag(tag string) Tag { + var b [4]byte + copy(b[:], []byte(tag)) + return Tag(pio.U32BE(b[:])) +} + +func FindChildrenByName(root Atom, tag string) Atom { + return FindChildren(root, StringToTag(tag)) +} + +func FindChildren(root Atom, tag Tag) Atom { + if root.Tag() == tag { + return root + } + for _, child := range root.Children() { + if r := FindChildren(child, tag); r != nil { + return r + } + } + return nil +} + +func ReadFileAtoms(r io.ReadSeeker) (atoms []Atom, err error) { + for { + offset, _ := r.Seek(0, 1) + taghdr := make([]byte, 8) + if _, err = io.ReadFull(r, taghdr); err != nil { + if err == io.EOF { + err = nil + } + return + } + size := pio.U32BE(taghdr[0:]) + tag := Tag(pio.U32BE(taghdr[4:])) + + var atom Atom + switch tag { + case FTYP: + atom = &FileType{} + case STYP: + atom = &SegmentType{} + case MOOV: + atom = &Movie{} + case MOOF: + atom = &MovieFrag{} + case SIDX: + atom = &SegmentIndex{} + } + + if atom != nil { + b := make([]byte, int(size)) + if _, err = io.ReadFull(r, b[8:]); err != nil { + return + } + copy(b, taghdr) + if _, err = atom.Unmarshal(b, int(offset)); err != nil { + return + } + atoms = append(atoms, atom) + } else { + dummy := &Dummy{Tag_: tag} + dummy.setPos(int(offset), int(size)) + if _, err = r.Seek(int64(size)-8, 1); err != nil { + return + } + atoms = append(atoms, dummy) + } + } +} + +func printatom(out io.Writer, root Atom, depth int) { + offset, size := root.Pos() + + type stringintf interface { + String() string + } + + fmt.Fprintf(out, + "%s%s offset=%d size=%d", + strings.Repeat(" ", depth*2), root.Tag(), offset, size, + ) + if str, ok := root.(stringintf); ok { + fmt.Fprint(out, " ", str.String()) + } + fmt.Fprintln(out) + + children := root.Children() + for _, child := range children { + printatom(out, child, depth+1) + } +} + +func FprintAtom(out io.Writer, root Atom) { + printatom(out, root, 0) +} + +func PrintAtom(root Atom) { + FprintAtom(os.Stdout, root) +} diff --git a/format/fmp4/fmp4io/avc1.go b/format/fmp4/fmp4io/avc1.go new file mode 100644 index 0000000..8b9427a --- /dev/null +++ b/format/fmp4/fmp4io/avc1.go @@ -0,0 +1,346 @@ +package fmp4io + +import "github.com/deepch/vdk/utils/bits/pio" + +const AVC1 = Tag(0x61766331) + +type AVC1Desc struct { + DataRefIdx int16 + Version int16 + Revision int16 + Vendor int32 + TemporalQuality int32 + SpatialQuality int32 + Width int16 + Height int16 + HorizontalResolution float64 + VorizontalResolution float64 + FrameCount int16 + CompressorName [32]byte + Depth int16 + ColorTableId int16 + Conf *AVC1Conf + PixelAspect *PixelAspect + Unknowns []Atom + AtomPos +} + +func (a AVC1Desc) Tag() Tag { + return AVC1 +} + +func (a AVC1Desc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(AVC1)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a AVC1Desc) marshal(b []byte) (n int) { + n += 6 + pio.PutI16BE(b[n:], a.DataRefIdx) + n += 2 + pio.PutI16BE(b[n:], a.Version) + n += 2 + pio.PutI16BE(b[n:], a.Revision) + n += 2 + pio.PutI32BE(b[n:], a.Vendor) + n += 4 + pio.PutI32BE(b[n:], a.TemporalQuality) + n += 4 + pio.PutI32BE(b[n:], a.SpatialQuality) + n += 4 + pio.PutI16BE(b[n:], a.Width) + n += 2 + pio.PutI16BE(b[n:], a.Height) + n += 2 + PutFixed32(b[n:], a.HorizontalResolution) + n += 4 + PutFixed32(b[n:], a.VorizontalResolution) + n += 4 + n += 4 + pio.PutI16BE(b[n:], a.FrameCount) + n += 2 + copy(b[n:], a.CompressorName[:]) + n += len(a.CompressorName[:]) + pio.PutI16BE(b[n:], a.Depth) + n += 2 + pio.PutI16BE(b[n:], a.ColorTableId) + n += 2 + if a.Conf != nil { + n += a.Conf.Marshal(b[n:]) + } + if a.PixelAspect != nil { + n += a.PixelAspect.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a AVC1Desc) Len() (n int) { + n += 8 + n += 6 + n += 2 + n += 2 + n += 2 + n += 4 + n += 4 + n += 4 + n += 2 + n += 2 + n += 4 + n += 4 + n += 4 + n += 2 + n += len(a.CompressorName[:]) + n += 2 + n += 2 + if a.Conf != nil { + n += a.Conf.Len() + } + if a.PixelAspect != nil { + n += a.PixelAspect.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *AVC1Desc) Unmarshal(b []byte, offset int) (n int, err error) { + a.AtomPos.setPos(offset, len(b)) + n += 8 + n += 6 + if len(b) < n+2 { + err = parseErr("DataRefIdx", n+offset, err) + return + } + a.DataRefIdx = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Revision", n+offset, err) + return + } + a.Revision = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("Vendor", n+offset, err) + return + } + a.Vendor = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TemporalQuality", n+offset, err) + return + } + a.TemporalQuality = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("SpatialQuality", n+offset, err) + return + } + a.SpatialQuality = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("Width", n+offset, err) + return + } + a.Width = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Height", n+offset, err) + return + } + a.Height = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("HorizontalResolution", n+offset, err) + return + } + a.HorizontalResolution = GetFixed32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("VorizontalResolution", n+offset, err) + return + } + a.VorizontalResolution = GetFixed32(b[n:]) + n += 4 + n += 4 + if len(b) < n+2 { + err = parseErr("FrameCount", n+offset, err) + return + } + a.FrameCount = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+len(a.CompressorName) { + err = parseErr("CompressorName", n+offset, err) + return + } + copy(a.CompressorName[:], b[n:]) + n += len(a.CompressorName) + if len(b) < n+2 { + err = parseErr("Depth", n+offset, err) + return + } + a.Depth = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("ColorTableId", n+offset, err) + return + } + a.ColorTableId = pio.I16BE(b[n:]) + n += 2 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case AVCC: + { + atom := &AVC1Conf{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("avcC", n+offset, err) + return + } + a.Conf = atom + } + case PASP: + { + atom := &PixelAspect{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("pasp", n+offset, err) + return + } + a.PixelAspect = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a AVC1Desc) Children() (r []Atom) { + if a.Conf != nil { + r = append(r, a.Conf) + } + if a.PixelAspect != nil { + r = append(r, a.PixelAspect) + } + r = append(r, a.Unknowns...) + return +} + +const AVCC = Tag(0x61766343) + +type AVC1Conf struct { + Data []byte + AtomPos +} + +func (a AVC1Conf) Tag() Tag { + return AVCC +} + +func (a AVC1Conf) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(AVCC)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a AVC1Conf) marshal(b []byte) (n int) { + copy(b[n:], a.Data[:]) + n += len(a.Data[:]) + return +} + +func (a AVC1Conf) Len() (n int) { + n += 8 + n += len(a.Data[:]) + return +} + +func (a *AVC1Conf) Unmarshal(b []byte, offset int) (n int, err error) { + a.AtomPos.setPos(offset, len(b)) + n += 8 + a.Data = b[n:] + n += len(b[n:]) + return +} + +func (a AVC1Conf) Children() (r []Atom) { + return +} + +const PASP = Tag(0x70617370) + +type PixelAspect struct { + HorizontalSpacing uint32 + VerticalSpacing uint32 + AtomPos +} + +func (a PixelAspect) Tag() Tag { + return PASP +} + +func (a PixelAspect) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(PASP)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a PixelAspect) marshal(b []byte) (n int) { + pio.PutU32BE(b[n:], a.HorizontalSpacing) + n += 4 + pio.PutU32BE(b[n:], a.VerticalSpacing) + n += 4 + return +} + +func (a PixelAspect) Len() (n int) { + return 8 + 8 +} + +func (a *PixelAspect) Unmarshal(b []byte, offset int) (n int, err error) { + a.AtomPos.setPos(offset, len(b)) + n += 8 + if len(b) < n+4 { + err = parseErr("HorizontalSpacing", n+offset, err) + return + } + a.HorizontalSpacing = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("VerticalSpacing", n+offset, err) + return + } + a.VerticalSpacing = pio.U32BE(b[n:]) + n += 4 + return +} + +func (a *PixelAspect) Children() (r []Atom) { + return nil +} diff --git a/format/fmp4/fmp4io/error.go b/format/fmp4/fmp4io/error.go new file mode 100644 index 0000000..0c51856 --- /dev/null +++ b/format/fmp4/fmp4io/error.go @@ -0,0 +1,37 @@ +package fmp4io + +import ( + "fmt" + "strings" +) + +type ParseError struct { + Debug string + Offset int + prev *ParseError + orig error +} + +func (a *ParseError) Error() string { + s := []string{} + for p := a; p != nil; p = p.prev { + s = append(s, fmt.Sprintf("%s:%d", p.Debug, p.Offset)) + if p.prev == nil && p.orig != nil { + s = append(s, p.orig.Error()) + } + } + return "mp4io: parse error: " + strings.Join(s, ",") +} + +func parseErr(debug string, offset int, prev error) (err error) { + _prev, _ := prev.(*ParseError) + if _prev != nil { + prev = nil + } + return &ParseError{ + Debug: debug, + Offset: offset, + prev: _prev, + orig: prev, + } +} diff --git a/format/fmp4/fmp4io/extend.go b/format/fmp4/fmp4io/extend.go new file mode 100644 index 0000000..f0d1cb3 --- /dev/null +++ b/format/fmp4/fmp4io/extend.go @@ -0,0 +1,192 @@ +package fmp4io + +import "github.com/deepch/vdk/utils/bits/pio" + +const MVEX = Tag(0x6d766578) + +type MovieExtend struct { + Tracks []*TrackExtend + Unknowns []Atom + AtomPos +} + +func (a MovieExtend) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MVEX)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a MovieExtend) marshal(b []byte) (n int) { + for _, atom := range a.Tracks { + n += atom.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a MovieExtend) Len() (n int) { + n += 8 + for _, atom := range a.Tracks { + n += atom.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *MovieExtend) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TREX: + { + atom := &TrackExtend{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trex", n+offset, err) + return + } + a.Tracks = append(a.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a MovieExtend) Children() (r []Atom) { + for _, atom := range a.Tracks { + r = append(r, atom) + } + r = append(r, a.Unknowns...) + return +} + +func (a MovieExtend) Tag() Tag { + return MVEX +} + +const TREX = Tag(0x74726578) + +type TrackExtend struct { + Version uint8 + Flags uint32 + TrackID uint32 + DefaultSampleDescIdx uint32 + DefaultSampleDuration uint32 + DefaultSampleSize uint32 + DefaultSampleFlags uint32 + AtomPos +} + +func (a TrackExtend) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TREX)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a TrackExtend) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], a.TrackID) + n += 4 + pio.PutU32BE(b[n:], a.DefaultSampleDescIdx) + n += 4 + pio.PutU32BE(b[n:], a.DefaultSampleDuration) + n += 4 + pio.PutU32BE(b[n:], a.DefaultSampleSize) + n += 4 + pio.PutU32BE(b[n:], a.DefaultSampleFlags) + n += 4 + return +} + +func (a TrackExtend) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + return +} + +func (a *TrackExtend) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("TrackID", n+offset, err) + return + } + a.TrackID = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleDescIdx", n+offset, err) + return + } + a.DefaultSampleDescIdx = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleDuration", n+offset, err) + return + } + a.DefaultSampleDuration = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleSize", n+offset, err) + return + } + a.DefaultSampleSize = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("DefaultSampleFlags", n+offset, err) + return + } + a.DefaultSampleFlags = pio.U32BE(b[n:]) + n += 4 + return +} + +func (a TrackExtend) Children() (r []Atom) { + return +} + +func (a TrackExtend) Tag() Tag { + return TREX +} diff --git a/format/fmp4/fmp4io/filetype.go b/format/fmp4/fmp4io/filetype.go new file mode 100644 index 0000000..e53d1f8 --- /dev/null +++ b/format/fmp4/fmp4io/filetype.go @@ -0,0 +1,103 @@ +package fmp4io + +import "github.com/deepch/vdk/utils/bits/pio" + +const FTYP = Tag(0x66747970) + +type FileType struct { + MajorBrand uint32 + MinorVersion uint32 + CompatibleBrands []uint32 + AtomPos +} + +func (t FileType) Tag() Tag { + return FTYP +} + +func (f FileType) Marshal(b []byte) (n int) { + l := 16 + 4*len(f.CompatibleBrands) + pio.PutU32BE(b, uint32(l)) + pio.PutU32BE(b[4:], uint32(FTYP)) + pio.PutU32BE(b[8:], f.MajorBrand) + pio.PutU32BE(b[12:], f.MinorVersion) + for i, v := range f.CompatibleBrands { + pio.PutU32BE(b[16+4*i:], v) + } + return l +} + +func (f FileType) Len() int { + return 16 + 4*len(f.CompatibleBrands) +} + +func (f *FileType) Unmarshal(b []byte, offset int) (n int, err error) { + f.AtomPos.setPos(offset, len(b)) + n = 8 + if len(b) < n+8 { + return 0, parseErr("MajorBrand", offset+n, nil) + } + f.MajorBrand = pio.U32BE(b[n:]) + n += 4 + f.MinorVersion = pio.U32BE(b[n:]) + n += 4 + for n < len(b)-3 { + f.CompatibleBrands = append(f.CompatibleBrands, pio.U32BE(b[n:])) + n += 4 + } + return +} + +func (f FileType) Children() []Atom { + return nil +} + +const STYP = Tag(0x73747970) + +type SegmentType struct { + MajorBrand uint32 + MinorVersion uint32 + CompatibleBrands []uint32 + AtomPos +} + +func (t SegmentType) Tag() Tag { + return STYP +} + +func (f SegmentType) Marshal(b []byte) (n int) { + l := 16 + 4*len(f.CompatibleBrands) + pio.PutU32BE(b, uint32(l)) + pio.PutU32BE(b[4:], uint32(STYP)) + pio.PutU32BE(b[8:], f.MajorBrand) + pio.PutU32BE(b[12:], f.MinorVersion) + for i, v := range f.CompatibleBrands { + pio.PutU32BE(b[16+4*i:], v) + } + return l +} + +func (f SegmentType) Len() int { + return 16 + 4*len(f.CompatibleBrands) +} + +func (f *SegmentType) Unmarshal(b []byte, offset int) (n int, err error) { + f.AtomPos.setPos(offset, len(b)) + n = 8 + if len(b) < n+8 { + return 0, parseErr("MajorBrand", offset+n, nil) + } + f.MajorBrand = pio.U32BE(b[n:]) + n += 4 + f.MinorVersion = pio.U32BE(b[n:]) + n += 4 + for n < len(b)-3 { + f.CompatibleBrands = append(f.CompatibleBrands, pio.U32BE(b[n:])) + n += 4 + } + return +} + +func (f SegmentType) Children() []Atom { + return nil +} diff --git a/format/fmp4/fmp4io/fragment.go b/format/fmp4/fmp4io/fragment.go new file mode 100644 index 0000000..791bae2 --- /dev/null +++ b/format/fmp4/fmp4io/fragment.go @@ -0,0 +1,765 @@ +package fmp4io + +import ( + "fmt" + + "github.com/deepch/vdk/utils/bits/pio" +) + +const MOOF = Tag(0x6d6f6f66) + +type MovieFrag struct { + Header *MovieFragHeader + Tracks []*TrackFrag + Unknowns []Atom + AtomPos +} + +func (a MovieFrag) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MOOF)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a MovieFrag) marshal(b []byte) (n int) { + if a.Header != nil { + n += a.Header.Marshal(b[n:]) + } + for _, atom := range a.Tracks { + n += atom.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a MovieFrag) Len() (n int) { + n += 8 + if a.Header != nil { + n += a.Header.Len() + } + for _, atom := range a.Tracks { + n += atom.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *MovieFrag) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MFHD: + { + atom := &MovieFragHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mfhd", n+offset, err) + return + } + a.Header = atom + } + case TRAF: + { + atom := &TrackFrag{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("traf", n+offset, err) + return + } + a.Tracks = append(a.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a MovieFrag) Children() (r []Atom) { + if a.Header != nil { + r = append(r, a.Header) + } + for _, atom := range a.Tracks { + r = append(r, atom) + } + r = append(r, a.Unknowns...) + return +} + +func (a MovieFrag) Tag() Tag { + return MOOF +} + +const MFHD = Tag(0x6d666864) + +type MovieFragHeader struct { + Version uint8 + Flags uint32 + Seqnum uint32 + AtomPos +} + +func (a MovieFragHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MFHD)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a MovieFragHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], a.Seqnum) + n += 4 + return +} + +func (a MovieFragHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + return +} + +func (a *MovieFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("Seqnum", n+offset, err) + return + } + a.Seqnum = pio.U32BE(b[n:]) + n += 4 + return +} + +func (a MovieFragHeader) Children() (r []Atom) { + return +} + +func (a MovieFragHeader) Tag() Tag { + return MFHD +} + +// TRUN is the atom type for TrackFragRun +const TRUN = Tag(0x7472756e) + +// TrackFragRun atom +type TrackFragRun struct { + Version uint8 + Flags TrackRunFlags + DataOffset uint32 + FirstSampleFlags SampleFlags + Entries []TrackFragRunEntry + AtomPos +} + +// TrackRunFlags is the type of TrackFragRun's Flags +type TrackRunFlags uint32 + +// Defined flags for TrackFragRun +const ( + TrackRunDataOffset TrackRunFlags = 0x01 + TrackRunFirstSampleFlags TrackRunFlags = 0x04 + TrackRunSampleDuration TrackRunFlags = 0x100 + TrackRunSampleSize TrackRunFlags = 0x200 + TrackRunSampleFlags TrackRunFlags = 0x400 + TrackRunSampleCTS TrackRunFlags = 0x800 +) + +func (a TrackFragRun) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRUN)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a TrackFragRun) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], uint32(a.Flags)) + n += 3 + pio.PutU32BE(b[n:], uint32(len(a.Entries))) + n += 4 + if a.Flags&TrackRunDataOffset != 0 { + { + pio.PutU32BE(b[n:], a.DataOffset) + n += 4 + } + } + if a.Flags&TrackRunFirstSampleFlags != 0 { + { + pio.PutU32BE(b[n:], uint32(a.FirstSampleFlags)) + n += 4 + } + } + + for _, entry := range a.Entries { + if a.Flags&TrackRunSampleDuration != 0 { + pio.PutU32BE(b[n:], entry.Duration) + n += 4 + } + if a.Flags&TrackRunSampleSize != 0 { + pio.PutU32BE(b[n:], entry.Size) + n += 4 + } + if a.Flags&TrackRunSampleFlags != 0 { + pio.PutU32BE(b[n:], uint32(entry.Flags)) + n += 4 + } + if a.Flags&TrackRunSampleCTS != 0 { + if a.Version > 0 { + pio.PutI32BE(b[:n], int32(entry.CTS)) + } else { + pio.PutU32BE(b[n:], uint32(entry.CTS)) + } + n += 4 + } + } + return +} + +func (a TrackFragRun) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if a.Flags&TrackRunDataOffset != 0 { + { + n += 4 + } + } + if a.Flags&TrackRunFirstSampleFlags != 0 { + { + n += 4 + } + } + + for range a.Entries { + if a.Flags&TrackRunSampleDuration != 0 { + n += 4 + } + if a.Flags&TrackRunSampleSize != 0 { + n += 4 + } + if a.Flags&TrackRunSampleFlags != 0 { + n += 4 + } + if a.Flags&TrackRunSampleCTS != 0 { + n += 4 + } + } + return +} + +func (a *TrackFragRun) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = TrackRunFlags(pio.U24BE(b[n:])) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + a.Entries = make([]TrackFragRunEntry, _len_Entries) + if a.Flags&TrackRunDataOffset != 0 { + { + if len(b) < n+4 { + err = parseErr("DataOffset", n+offset, err) + return + } + a.DataOffset = pio.U32BE(b[n:]) + n += 4 + } + } + if a.Flags&TrackRunFirstSampleFlags != 0 { + { + if len(b) < n+4 { + err = parseErr("FirstSampleFlags", n+offset, err) + return + } + a.FirstSampleFlags = SampleFlags(pio.U32BE(b[n:])) + n += 4 + } + } + + for i := 0; i < int(_len_Entries); i++ { + entry := &a.Entries[i] + if a.Flags&TrackRunSampleDuration != 0 { + entry.Duration = pio.U32BE(b[n:]) + n += 4 + } + if a.Flags&TrackRunSampleSize != 0 { + entry.Size = pio.U32BE(b[n:]) + n += 4 + } + if a.Flags&TrackRunSampleFlags != 0 { + entry.Flags = SampleFlags(pio.U32BE(b[n:])) + n += 4 + } + if a.Flags&TrackRunSampleCTS != 0 { + if a.Version > 0 { + entry.CTS = int32(pio.I32BE(b[n:])) + } else { + entry.CTS = int32(pio.U32BE(b[n:])) + } + n += 4 + } + } + return +} + +func (a TrackFragRun) Children() (r []Atom) { + return +} + +type TrackFragRunEntry struct { + Duration uint32 + Size uint32 + Flags SampleFlags + CTS int32 +} + +func (a TrackFragRun) Tag() Tag { + return TRUN +} + +const TFDT = Tag(0x74666474) + +type TrackFragDecodeTime struct { + Version uint8 + Flags uint32 + Time uint64 + AtomPos +} + +func (a TrackFragDecodeTime) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TFDT)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a TrackFragDecodeTime) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + if a.Version != 0 { + pio.PutU64BE(b[n:], a.Time) + n += 8 + } else { + pio.PutU32BE(b[n:], uint32(a.Time)) + n += 4 + } + return +} + +func (a TrackFragDecodeTime) Len() (n int) { + n += 8 + n += 1 + n += 3 + if a.Version != 0 { + n += 8 + } else { + + n += 4 + } + return +} + +func (a *TrackFragDecodeTime) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if a.Version != 0 { + a.Time = pio.U64BE(b[n:]) + n += 8 + } else { + a.Time = uint64(pio.U32BE(b[n:])) + n += 4 + } + return +} + +func (a TrackFragDecodeTime) Children() (r []Atom) { + return +} + +func (a TrackFragDecodeTime) Tag() Tag { + return TFDT +} + +const TRAF = Tag(0x74726166) + +type TrackFrag struct { + Header *TrackFragHeader + DecodeTime *TrackFragDecodeTime + Run *TrackFragRun + Unknowns []Atom + AtomPos +} + +func (a TrackFrag) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRAF)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a TrackFrag) marshal(b []byte) (n int) { + if a.Header != nil { + n += a.Header.Marshal(b[n:]) + } + if a.DecodeTime != nil { + n += a.DecodeTime.Marshal(b[n:]) + } + if a.Run != nil { + n += a.Run.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a TrackFrag) Len() (n int) { + n += 8 + if a.Header != nil { + n += a.Header.Len() + } + if a.DecodeTime != nil { + n += a.DecodeTime.Len() + } + if a.Run != nil { + n += a.Run.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *TrackFrag) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TFHD: + { + atom := &TrackFragHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tfhd", n+offset, err) + return + } + a.Header = atom + } + case TFDT: + { + atom := &TrackFragDecodeTime{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tfdt", n+offset, err) + return + } + a.DecodeTime = atom + } + case TRUN: + { + atom := &TrackFragRun{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trun", n+offset, err) + return + } + a.Run = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a TrackFrag) Children() (r []Atom) { + if a.Header != nil { + r = append(r, a.Header) + } + if a.DecodeTime != nil { + r = append(r, a.DecodeTime) + } + if a.Run != nil { + r = append(r, a.Run) + } + r = append(r, a.Unknowns...) + return +} + +func (a TrackFrag) Tag() Tag { + return TRAF +} + +func (a TrackFragRun) String() string { + return fmt.Sprintf("dataoffset=%d", a.DataOffset) +} + +// TFHD is the atom type for TrackFragHeader +const TFHD = Tag(0x74666864) + +// TrackFragHeader atom +type TrackFragHeader struct { + Version uint8 + Flags TrackFragFlags + TrackID uint32 + BaseDataOffset uint64 + StsdID uint32 + DefaultDuration uint32 + DefaultSize uint32 + DefaultFlags SampleFlags + AtomPos +} + +// TrackFragFlags is the type of TrackFragHeader's Flags +type TrackFragFlags uint32 + +// Defined flags for TrackFragHeader +const ( + TrackFragBaseDataOffset TrackFragFlags = 0x01 + TrackFragStsdID TrackFragFlags = 0x02 + TrackFragDefaultDuration TrackFragFlags = 0x08 + TrackFragDefaultSize TrackFragFlags = 0x10 + TrackFragDefaultFlags TrackFragFlags = 0x20 + TrackFragDurationIsEmpty TrackFragFlags = 0x010000 + TrackFragDefaultBaseIsMOOF TrackFragFlags = 0x020000 +) + +func (a TrackFragHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TFHD)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a TrackFragHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], uint32(a.Flags)) + n += 3 + pio.PutU32BE(b[n:], a.TrackID) + n += 4 + if a.Flags&TrackFragBaseDataOffset != 0 { + { + pio.PutU64BE(b[n:], a.BaseDataOffset) + n += 8 + } + } + if a.Flags&TrackFragStsdID != 0 { + { + pio.PutU32BE(b[n:], a.StsdID) + n += 4 + } + } + if a.Flags&TrackFragDefaultDuration != 0 { + { + pio.PutU32BE(b[n:], a.DefaultDuration) + n += 4 + } + } + if a.Flags&TrackFragDefaultSize != 0 { + { + pio.PutU32BE(b[n:], a.DefaultSize) + n += 4 + } + } + if a.Flags&TrackFragDefaultFlags != 0 { + { + pio.PutU32BE(b[n:], uint32(a.DefaultFlags)) + n += 4 + } + } + return +} + +func (a TrackFragHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if a.Flags&TrackFragBaseDataOffset != 0 { + { + n += 8 + } + } + if a.Flags&TrackFragStsdID != 0 { + { + n += 4 + } + } + if a.Flags&TrackFragDefaultDuration != 0 { + { + n += 4 + } + } + if a.Flags&TrackFragDefaultSize != 0 { + { + n += 4 + } + } + if a.Flags&TrackFragDefaultFlags != 0 { + { + n += 4 + } + } + return +} + +func (a *TrackFragHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = TrackFragFlags(pio.U24BE(b[n:])) + n += 3 + if len(b) < n+4 { + err = parseErr("TrackID", n+offset, err) + return + } + a.TrackID = pio.U32BE(b[n:]) + n += 4 + if a.Flags&TrackFragBaseDataOffset != 0 { + { + if len(b) < n+8 { + err = parseErr("BaseDataOffset", n+offset, err) + return + } + a.BaseDataOffset = pio.U64BE(b[n:]) + n += 8 + } + } + if a.Flags&TrackFragStsdID != 0 { + { + if len(b) < n+4 { + err = parseErr("StsdId", n+offset, err) + return + } + a.StsdID = pio.U32BE(b[n:]) + n += 4 + } + } + if a.Flags&TrackFragDefaultDuration != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultDuration", n+offset, err) + return + } + a.DefaultDuration = pio.U32BE(b[n:]) + n += 4 + } + } + if a.Flags&TrackFragDefaultSize != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultSize", n+offset, err) + return + } + a.DefaultSize = pio.U32BE(b[n:]) + n += 4 + } + } + if a.Flags&TrackFragDefaultFlags != 0 { + { + if len(b) < n+4 { + err = parseErr("DefaultFlags", n+offset, err) + return + } + a.DefaultFlags = SampleFlags(pio.U32BE(b[n:])) + n += 4 + } + } + return +} + +func (a TrackFragHeader) Children() (r []Atom) { + return +} + +func (a TrackFragHeader) Tag() Tag { + return TFHD +} + +func (a TrackFragHeader) String() string { + return fmt.Sprintf("basedataoffset=%d", a.BaseDataOffset) +} diff --git a/format/fmp4/fmp4io/marshal.go b/format/fmp4/fmp4io/marshal.go new file mode 100644 index 0000000..ee1f925 --- /dev/null +++ b/format/fmp4/fmp4io/marshal.go @@ -0,0 +1,64 @@ +package fmp4io + +import ( + "math" + "time" + + "github.com/deepch/vdk/utils/bits/pio" +) + +func GetTime32(b []byte) (t time.Time) { + sec := pio.U32BE(b) + if sec != 0 { + t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) + t = t.Add(time.Second * time.Duration(sec)) + } + return +} + +func PutTime32(b []byte, t time.Time) { + var sec uint32 + if !t.IsZero() { + dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) + sec = uint32(dur / time.Second) + } + pio.PutU32BE(b, sec) +} + +func GetTime64(b []byte) (t time.Time) { + sec := pio.U64BE(b) + if sec != 0 { + t = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) + t = t.Add(time.Second * time.Duration(sec)) + } + return +} + +func PutTime64(b []byte, t time.Time) { + var sec uint64 + if !t.IsZero() { + dur := t.Sub(time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)) + sec = uint64(dur / time.Second) + } + pio.PutU64BE(b, sec) +} + +func PutFixed16(b []byte, f float64) { + intpart, fracpart := math.Modf(f) + b[0] = uint8(intpart) + b[1] = uint8(fracpart * 256.0) +} + +func GetFixed16(b []byte) float64 { + return float64(b[0]) + float64(b[1])/256.0 +} + +func PutFixed32(b []byte, f float64) { + intpart, fracpart := math.Modf(f) + pio.PutU16BE(b[0:2], uint16(intpart)) + pio.PutU16BE(b[2:4], uint16(fracpart*65536.0)) +} + +func GetFixed32(b []byte) float64 { + return float64(pio.U16BE(b[0:2])) + float64(pio.U16BE(b[2:4]))/65536.0 +} diff --git a/format/fmp4/fmp4io/media.go b/format/fmp4/fmp4io/media.go new file mode 100644 index 0000000..71623cd --- /dev/null +++ b/format/fmp4/fmp4io/media.go @@ -0,0 +1,616 @@ +package fmp4io + +import ( + "time" + + "github.com/deepch/vdk/utils/bits/pio" +) + +const MDIA = Tag(0x6d646961) + +type Media struct { + Header *MediaHeader + Handler *HandlerRefer + Info *MediaInfo + Unknowns []Atom + AtomPos +} + +func (a Media) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MDIA)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a Media) marshal(b []byte) (n int) { + if a.Header != nil { + n += a.Header.Marshal(b[n:]) + } + if a.Handler != nil { + n += a.Handler.Marshal(b[n:]) + } + if a.Info != nil { + n += a.Info.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a Media) Len() (n int) { + n += 8 + if a.Header != nil { + n += a.Header.Len() + } + if a.Handler != nil { + n += a.Handler.Len() + } + if a.Info != nil { + n += a.Info.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *Media) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MDHD: + { + atom := &MediaHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mdhd", n+offset, err) + return + } + a.Header = atom + } + case HDLR: + { + atom := &HandlerRefer{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("hdlr", n+offset, err) + return + } + a.Handler = atom + } + case MINF: + { + atom := &MediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("minf", n+offset, err) + return + } + a.Info = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a Media) Children() (r []Atom) { + if a.Header != nil { + r = append(r, a.Header) + } + if a.Handler != nil { + r = append(r, a.Handler) + } + if a.Info != nil { + r = append(r, a.Info) + } + r = append(r, a.Unknowns...) + return +} + +func (a Media) Tag() Tag { + return MDIA +} + +const MDHD = Tag(0x6d646864) + +type MediaHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TimeScale uint32 + Duration uint32 + Language int16 + Quality int16 + AtomPos +} + +func (a MediaHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MDHD)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a MediaHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + PutTime32(b[n:], a.CreateTime) + n += 4 + PutTime32(b[n:], a.ModifyTime) + n += 4 + pio.PutU32BE(b[n:], a.TimeScale) + n += 4 + pio.PutU32BE(b[n:], a.Duration) + n += 4 + pio.PutI16BE(b[n:], a.Language) + n += 2 + pio.PutI16BE(b[n:], a.Quality) + n += 2 + return +} + +func (a MediaHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 2 + n += 2 + return +} + +func (a *MediaHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + a.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + a.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TimeScale", n+offset, err) + return + } + a.TimeScale = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + a.Duration = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("Language", n+offset, err) + return + } + a.Language = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Quality", n+offset, err) + return + } + a.Quality = pio.I16BE(b[n:]) + n += 2 + return +} + +func (a MediaHeader) Children() (r []Atom) { + return +} + +func (a MediaHeader) Tag() Tag { + return MDHD +} + +const MINF = Tag(0x6d696e66) + +type MediaInfo struct { + Sound *SoundMediaInfo + Video *VideoMediaInfo + Data *DataInfo + Sample *SampleTable + Unknowns []Atom + AtomPos +} + +func (a MediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MINF)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a MediaInfo) marshal(b []byte) (n int) { + if a.Sound != nil { + n += a.Sound.Marshal(b[n:]) + } + if a.Video != nil { + n += a.Video.Marshal(b[n:]) + } + if a.Data != nil { + n += a.Data.Marshal(b[n:]) + } + if a.Sample != nil { + n += a.Sample.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a MediaInfo) Len() (n int) { + n += 8 + if a.Sound != nil { + n += a.Sound.Len() + } + if a.Video != nil { + n += a.Video.Len() + } + if a.Data != nil { + n += a.Data.Len() + } + if a.Sample != nil { + n += a.Sample.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *MediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case SMHD: + { + atom := &SoundMediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("smhd", n+offset, err) + return + } + a.Sound = atom + } + case VMHD: + { + atom := &VideoMediaInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("vmhd", n+offset, err) + return + } + a.Video = atom + } + case DINF: + { + atom := &DataInfo{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("dinf", n+offset, err) + return + } + a.Data = atom + } + case STBL: + { + atom := &SampleTable{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stbl", n+offset, err) + return + } + a.Sample = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a MediaInfo) Children() (r []Atom) { + if a.Sound != nil { + r = append(r, a.Sound) + } + if a.Video != nil { + r = append(r, a.Video) + } + if a.Data != nil { + r = append(r, a.Data) + } + if a.Sample != nil { + r = append(r, a.Sample) + } + r = append(r, a.Unknowns...) + return +} + +func (a MediaInfo) Tag() Tag { + return MINF +} + +const VMHD = Tag(0x766d6864) + +type VideoMediaInfo struct { + Version uint8 + Flags uint32 + GraphicsMode int16 + Opcolor [3]int16 + AtomPos +} + +func (a VideoMediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(VMHD)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a VideoMediaInfo) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutI16BE(b[n:], a.GraphicsMode) + n += 2 + for _, entry := range a.Opcolor { + pio.PutI16BE(b[n:], entry) + n += 2 + } + return +} + +func (a VideoMediaInfo) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 2 + n += 2 * len(a.Opcolor[:]) + return +} + +func (a *VideoMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+2 { + err = parseErr("GraphicsMode", n+offset, err) + return + } + a.GraphicsMode = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2*len(a.Opcolor) { + err = parseErr("Opcolor", n+offset, err) + return + } + for i := range a.Opcolor { + a.Opcolor[i] = pio.I16BE(b[n:]) + n += 2 + } + return +} + +func (a VideoMediaInfo) Children() (r []Atom) { + return +} + +func (a VideoMediaInfo) Tag() Tag { + return VMHD +} + +const SMHD = Tag(0x736d6864) + +type SoundMediaInfo struct { + Version uint8 + Flags uint32 + Balance int16 + AtomPos +} + +func (a SoundMediaInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(SMHD)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a SoundMediaInfo) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutI16BE(b[n:], a.Balance) + n += 2 + n += 2 + return +} + +func (a SoundMediaInfo) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 2 + n += 2 + return +} + +func (a *SoundMediaInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+2 { + err = parseErr("Balance", n+offset, err) + return + } + a.Balance = pio.I16BE(b[n:]) + n += 2 + n += 2 + return +} + +func (a SoundMediaInfo) Children() (r []Atom) { + return +} + +func (a SoundMediaInfo) Tag() Tag { + return SMHD +} + +const DINF = Tag(0x64696e66) + +func (a DataInfo) Tag() Tag { + return DINF +} + +type DataInfo struct { + Refer *DataRefer + Unknowns []Atom + AtomPos +} + +func (a DataInfo) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(DINF)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a DataInfo) marshal(b []byte) (n int) { + if a.Refer != nil { + n += a.Refer.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a DataInfo) Len() (n int) { + n += 8 + if a.Refer != nil { + n += a.Refer.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *DataInfo) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case DREF: + { + atom := &DataRefer{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("dref", n+offset, err) + return + } + a.Refer = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a DataInfo) Children() (r []Atom) { + if a.Refer != nil { + r = append(r, a.Refer) + } + r = append(r, a.Unknowns...) + return +} diff --git a/format/fmp4/fmp4io/movie.go b/format/fmp4/fmp4io/movie.go new file mode 100644 index 0000000..6d90e0f --- /dev/null +++ b/format/fmp4/fmp4io/movie.go @@ -0,0 +1,564 @@ +package fmp4io + +import ( + "fmt" + "time" + + "github.com/deepch/vdk/utils/bits/pio" +) + +const MOOV = Tag(0x6d6f6f76) + +type Movie struct { + Header *MovieHeader + MovieExtend *MovieExtend + Tracks []*Track + Unknowns []Atom + AtomPos +} + +func (a Movie) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MOOV)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a Movie) marshal(b []byte) (n int) { + if a.Header != nil { + n += a.Header.Marshal(b[n:]) + } + for _, atom := range a.Tracks { + n += atom.Marshal(b[n:]) + } + if a.MovieExtend != nil { + n += a.MovieExtend.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a Movie) Len() (n int) { + n += 8 + if a.Header != nil { + n += a.Header.Len() + } + for _, atom := range a.Tracks { + n += atom.Len() + } + if a.MovieExtend != nil { + n += a.MovieExtend.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *Movie) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case MVHD: + { + atom := &MovieHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mvhd", n+offset, err) + return + } + a.Header = atom + } + case MVEX: + { + atom := &MovieExtend{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mvex", n+offset, err) + return + } + a.MovieExtend = atom + } + case TRAK: + { + atom := &Track{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("trak", n+offset, err) + return + } + a.Tracks = append(a.Tracks, atom) + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a Movie) Children() (r []Atom) { + if a.Header != nil { + r = append(r, a.Header) + } + if a.MovieExtend != nil { + r = append(r, a.MovieExtend) + } + for _, atom := range a.Tracks { + r = append(r, atom) + } + r = append(r, a.Unknowns...) + return +} + +func (a Movie) Tag() Tag { + return MOOV +} + +const MVHD = Tag(0x6d766864) + +type MovieHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TimeScale uint32 + Duration uint32 + PreferredRate float64 + PreferredVolume float64 + Matrix [9]int32 + NextTrackID uint32 + AtomPos +} + +func (a MovieHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MVHD)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a MovieHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + PutTime32(b[n:], a.CreateTime) + n += 4 + PutTime32(b[n:], a.ModifyTime) + n += 4 + pio.PutU32BE(b[n:], a.TimeScale) + n += 4 + pio.PutU32BE(b[n:], a.Duration) + n += 4 + PutFixed32(b[n:], a.PreferredRate) + n += 4 + PutFixed16(b[n:], a.PreferredVolume) + n += 2 + n += 10 + for _, entry := range a.Matrix { + pio.PutI32BE(b[n:], entry) + n += 4 + } + n += 24 + pio.PutU32BE(b[n:], a.NextTrackID) + n += 4 + return +} + +func (a MovieHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 2 + n += 10 + n += 4 * len(a.Matrix[:]) + n += 24 + n += 4 + return +} + +func (a *MovieHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + a.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + a.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TimeScale", n+offset, err) + return + } + a.TimeScale = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + a.Duration = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("PreferredRate", n+offset, err) + return + } + a.PreferredRate = GetFixed32(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("PreferredVolume", n+offset, err) + return + } + a.PreferredVolume = GetFixed16(b[n:]) + n += 2 + n += 10 + if len(b) < n+4*len(a.Matrix) { + err = parseErr("Matrix", n+offset, err) + return + } + for i := range a.Matrix { + a.Matrix[i] = pio.I32BE(b[n:]) + n += 4 + } + n += 24 + if len(b) < n+4 { + err = parseErr("NextTrackID", n+offset, err) + return + } + a.NextTrackID = pio.U32BE(b[n:]) + n += 4 + return +} + +func (a MovieHeader) Children() (r []Atom) { + return +} + +func (a MovieHeader) Tag() Tag { + return MVHD +} + +func (a MovieHeader) String() string { + return fmt.Sprintf("dur=%d", a.Duration) +} + +const TRAK = Tag(0x7472616b) + +type Track struct { + Header *TrackHeader + Media *Media + Unknowns []Atom + AtomPos +} + +func (a Track) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TRAK)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a Track) marshal(b []byte) (n int) { + if a.Header != nil { + n += a.Header.Marshal(b[n:]) + } + if a.Media != nil { + n += a.Media.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a Track) Len() (n int) { + n += 8 + if a.Header != nil { + n += a.Header.Len() + } + if a.Media != nil { + n += a.Media.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *Track) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case TKHD: + { + atom := &TrackHeader{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("tkhd", n+offset, err) + return + } + a.Header = atom + } + case MDIA: + { + atom := &Media{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mdia", n+offset, err) + return + } + a.Media = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a Track) Children() (r []Atom) { + if a.Header != nil { + r = append(r, a.Header) + } + if a.Media != nil { + r = append(r, a.Media) + } + r = append(r, a.Unknowns...) + return +} + +func (a Track) Tag() Tag { + return TRAK +} + +func (a *Track) GetAVC1Conf() (conf *AVC1Conf) { + atom := FindChildren(a, AVCC) + conf, _ = atom.(*AVC1Conf) + return +} + +func (a *Track) GetElemStreamDesc() (esds *ElemStreamDesc) { + atom := FindChildren(a, ESDS) + esds, _ = atom.(*ElemStreamDesc) + return +} + +const TKHD = Tag(0x746b6864) + +type TrackHeader struct { + Version uint8 + Flags uint32 + CreateTime time.Time + ModifyTime time.Time + TrackID uint32 + Duration uint32 + Layer int16 + AlternateGroup int16 + Volume float64 + Matrix [9]int32 + TrackWidth float64 + TrackHeight float64 + AtomPos +} + +func (a TrackHeader) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(TKHD)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a TrackHeader) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + PutTime32(b[n:], a.CreateTime) + n += 4 + PutTime32(b[n:], a.ModifyTime) + n += 4 + pio.PutU32BE(b[n:], a.TrackID) + n += 4 + n += 4 + pio.PutU32BE(b[n:], a.Duration) + n += 4 + n += 8 + pio.PutI16BE(b[n:], a.Layer) + n += 2 + pio.PutI16BE(b[n:], a.AlternateGroup) + n += 2 + PutFixed16(b[n:], a.Volume) + n += 2 + n += 2 + for _, entry := range a.Matrix { + pio.PutI32BE(b[n:], entry) + n += 4 + } + PutFixed32(b[n:], a.TrackWidth) + n += 4 + PutFixed32(b[n:], a.TrackHeight) + n += 4 + return +} + +func (a TrackHeader) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 4 + n += 4 + n += 4 + n += 8 + n += 2 + n += 2 + n += 2 + n += 2 + n += 4 * len(a.Matrix[:]) + n += 4 + n += 4 + return +} + +func (a *TrackHeader) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("CreateTime", n+offset, err) + return + } + a.CreateTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("ModifyTime", n+offset, err) + return + } + a.ModifyTime = GetTime32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TrackId", n+offset, err) + return + } + a.TrackID = pio.U32BE(b[n:]) + n += 4 + n += 4 + if len(b) < n+4 { + err = parseErr("Duration", n+offset, err) + return + } + a.Duration = pio.U32BE(b[n:]) + n += 4 + n += 8 + if len(b) < n+2 { + err = parseErr("Layer", n+offset, err) + return + } + a.Layer = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("AlternateGroup", n+offset, err) + return + } + a.AlternateGroup = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Volume", n+offset, err) + return + } + a.Volume = GetFixed16(b[n:]) + n += 2 + n += 2 + if len(b) < n+4*len(a.Matrix) { + err = parseErr("Matrix", n+offset, err) + return + } + for i := range a.Matrix { + a.Matrix[i] = pio.I32BE(b[n:]) + n += 4 + } + if len(b) < n+4 { + err = parseErr("TrackWidth", n+offset, err) + return + } + a.TrackWidth = GetFixed32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TrackHeight", n+offset, err) + return + } + a.TrackHeight = GetFixed32(b[n:]) + n += 4 + return +} + +func (a TrackHeader) Children() (r []Atom) { + return +} + +func (a TrackHeader) Tag() Tag { + return TKHD +} + +const MDAT = Tag(0x6d646174) diff --git a/format/fmp4/fmp4io/mp4a.go b/format/fmp4/fmp4io/mp4a.go new file mode 100644 index 0000000..c19a826 --- /dev/null +++ b/format/fmp4/fmp4io/mp4a.go @@ -0,0 +1,222 @@ +package fmp4io + +import ( + "github.com/deepch/vdk/format/fmp4/esio" + "github.com/deepch/vdk/utils/bits/pio" +) + +const MP4A = Tag(0x6d703461) + +type MP4ADesc struct { + DataRefIdx int16 + Version int16 + RevisionLevel int16 + Vendor int32 + NumberOfChannels int16 + SampleSize int16 + CompressionId int16 + SampleRate float64 + Conf *ElemStreamDesc + Unknowns []Atom + AtomPos +} + +func (a MP4ADesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(MP4A)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a MP4ADesc) marshal(b []byte) (n int) { + n += 6 + pio.PutI16BE(b[n:], a.DataRefIdx) + n += 2 + pio.PutI16BE(b[n:], a.Version) + n += 2 + pio.PutI16BE(b[n:], a.RevisionLevel) + n += 2 + pio.PutI32BE(b[n:], a.Vendor) + n += 4 + pio.PutI16BE(b[n:], a.NumberOfChannels) + n += 2 + pio.PutI16BE(b[n:], a.SampleSize) + n += 2 + pio.PutI16BE(b[n:], a.CompressionId) + n += 2 + n += 2 + PutFixed32(b[n:], a.SampleRate) + n += 4 + if a.Conf != nil { + n += a.Conf.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a MP4ADesc) Len() (n int) { + n += 8 + n += 6 + n += 2 + n += 2 + n += 2 + n += 4 + n += 2 + n += 2 + n += 2 + n += 2 + n += 4 + if a.Conf != nil { + n += a.Conf.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *MP4ADesc) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + n += 6 + if len(b) < n+2 { + err = parseErr("DataRefIdx", n+offset, err) + return + } + a.DataRefIdx = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("RevisionLevel", n+offset, err) + return + } + a.RevisionLevel = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("Vendor", n+offset, err) + return + } + a.Vendor = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("NumberOfChannels", n+offset, err) + return + } + a.NumberOfChannels = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("SampleSize", n+offset, err) + return + } + a.SampleSize = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("CompressionId", n+offset, err) + return + } + a.CompressionId = pio.I16BE(b[n:]) + n += 2 + n += 2 + if len(b) < n+4 { + err = parseErr("SampleRate", n+offset, err) + return + } + a.SampleRate = GetFixed32(b[n:]) + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case ESDS: + { + atom := &ElemStreamDesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("esds", n+offset, err) + return + } + a.Conf = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a MP4ADesc) Children() (r []Atom) { + if a.Conf != nil { + r = append(r, a.Conf) + } + r = append(r, a.Unknowns...) + return +} + +func (a MP4ADesc) Tag() Tag { + return MP4A +} + +const ESDS = Tag(0x65736473) + +type ElemStreamDesc struct { + StreamDescriptor *esio.StreamDescriptor + AtomPos +} + +func (a ElemStreamDesc) Children() []Atom { + return nil +} + +func (a ElemStreamDesc) Len() (n int) { + blob, _ := a.StreamDescriptor.Marshal() + return 8 + 4 + len(blob) +} + +func (a ElemStreamDesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(ESDS)) + n += 8 + pio.PutU32BE(b[n:], 0) // Version + n += 4 + blob, err := a.StreamDescriptor.Marshal() + if err != nil { + panic(err) + } + copy(b[n:], blob) + n += len(blob) + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a *ElemStreamDesc) Unmarshal(b []byte, offset int) (n int, err error) { + if len(b) < n+12 { + err = parseErr("hdr", offset+n, err) + return + } + a.AtomPos.setPos(offset, len(b)) + var remainder []byte + a.StreamDescriptor, remainder, err = esio.ParseStreamDescriptor(b[12:]) + n += len(b) - len(remainder) + return +} + +func (a ElemStreamDesc) Tag() Tag { + return ESDS +} diff --git a/format/fmp4/fmp4io/opus.go b/format/fmp4/fmp4io/opus.go new file mode 100644 index 0000000..2eaefe7 --- /dev/null +++ b/format/fmp4/fmp4io/opus.go @@ -0,0 +1,202 @@ +package fmp4io + +import ( + "github.com/deepch/vdk/utils/bits/pio" +) + +const ( + OPUS = Tag(0x4f707573) + DOPS = Tag(0x644f7073) +) + +type OpusSampleEntry struct { + DataRefIdx uint16 + NumberOfChannels uint16 + SampleSize uint16 + CompressionID uint16 + SampleRate float64 + Conf *OpusSpecificConfiguration + AtomPos +} + +func (a OpusSampleEntry) Tag() Tag { return OPUS } + +func (a OpusSampleEntry) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(OPUS)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a OpusSampleEntry) marshal(b []byte) (n int) { + n += 6 + pio.PutU16BE(b[n:], a.DataRefIdx) + n += 2 + n += 8 + pio.PutU16BE(b[n:], a.NumberOfChannels) + n += 2 + pio.PutU16BE(b[n:], a.SampleSize) + n += 2 + n += 4 + PutFixed32(b[n:], a.SampleRate) + n += 4 + if a.Conf != nil { + n += a.Conf.Marshal(b[n:]) + } + return +} + +func (a OpusSampleEntry) Len() (n int) { + n += 8 + n += 6 + n += 2 + n += 8 + n += 2 + n += 2 + n += 4 + n += 4 + if a.Conf != nil { + n += a.Conf.Len() + } + return +} + +func (a *OpusSampleEntry) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + n += 6 + if len(b) < n+2 { + err = parseErr("DataRefIdx", n+offset, err) + return + } + a.DataRefIdx = pio.U16BE(b[n:]) + n += 2 + n += 2 + n += 2 + n += 4 + if len(b) < n+2 { + err = parseErr("NumberOfChannels", n+offset, err) + return + } + a.NumberOfChannels = pio.U16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("SampleSize", n+offset, err) + return + } + a.SampleSize = pio.U16BE(b[n:]) + n += 2 + n += 2 + n += 2 + if len(b) < n+4 { + err = parseErr("SampleRate", n+offset, err) + return + } + a.SampleRate = GetFixed32(b[n:]) + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case DOPS: + { + atom := &OpusSpecificConfiguration{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("esds", n+offset, err) + return + } + a.Conf = atom + } + } + n += size + } + return +} + +func (a OpusSampleEntry) Children() (r []Atom) { + if a.Conf != nil { + r = append(r, a.Conf) + } + return +} + +type OpusSpecificConfiguration struct { + Version uint8 + OutputChannelCount uint8 + PreSkip uint16 + InputSampleRate uint32 + OutputGain int16 + ChannelMappingFamily uint8 + AtomPos +} + +func (a OpusSpecificConfiguration) Tag() Tag { return DOPS } +func (a OpusSpecificConfiguration) Children() []Atom { return nil } + +func (a OpusSpecificConfiguration) Len() (n int) { + n += 8 + n++ + n++ + n += 2 + n += 4 + n += 2 + n++ + return +} + +func (a OpusSpecificConfiguration) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(DOPS)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a OpusSpecificConfiguration) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n++ + pio.PutU8(b[n:], a.OutputChannelCount) + n++ + pio.PutU16BE(b[n:], a.PreSkip) + n += 2 + pio.PutU32BE(b[n:], a.InputSampleRate) + n += 4 + pio.PutI16BE(b[n:], a.OutputGain) + n += 2 + pio.PutU8(b[n:], a.ChannelMappingFamily) + n++ + return +} + +func (a *OpusSpecificConfiguration) Unmarshal(b []byte, offset int) (n int, err error) { + a.setPos(offset, len(b)) + n += 8 + if len(b) < 8+11 { + err = parseErr("OpusSpecificConfiguration", offset, nil) + return + } + a.Version = b[n] + if a.Version != 0 { + err = parseErr("unknown version", offset, nil) + return + } + n++ + a.OutputChannelCount = b[n] + n++ + a.PreSkip = pio.U16BE(b[n:]) + n += 2 + a.InputSampleRate = pio.U32BE(b[n:]) + n += 4 + a.OutputGain = pio.I16BE(b[n:]) + n += 2 + a.ChannelMappingFamily = b[n] + if a.ChannelMappingFamily != 0 { + err = parseErr("ChannelMappingFamily", offset+n, nil) + return + } + n++ + return +} diff --git a/format/fmp4/fmp4io/refer.go b/format/fmp4/fmp4io/refer.go new file mode 100644 index 0000000..49b3fbe --- /dev/null +++ b/format/fmp4/fmp4io/refer.go @@ -0,0 +1,251 @@ +package fmp4io + +import ( + "bytes" + + "github.com/deepch/vdk/utils/bits/pio" +) + +const DREF = Tag(0x64726566) + +type DataRefer struct { + Version uint8 + Flags uint32 + Url *DataReferUrl + AtomPos +} + +func (a DataRefer) Tag() Tag { + return DREF +} + +func (a DataRefer) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(DREF)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a DataRefer) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + _childrenNR := 0 + if a.Url != nil { + _childrenNR++ + } + pio.PutI32BE(b[n:], int32(_childrenNR)) + n += 4 + if a.Url != nil { + n += a.Url.Marshal(b[n:]) + } + return +} + +func (a DataRefer) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if a.Url != nil { + n += a.Url.Len() + } + return +} + +func (a *DataRefer) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case URL: + { + atom := &DataReferUrl{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("url ", n+offset, err) + return + } + a.Url = atom + } + } + n += size + } + return +} + +func (a DataRefer) Children() (r []Atom) { + if a.Url != nil { + r = append(r, a.Url) + } + return +} + +const URL = Tag(0x75726c20) + +type DataReferUrl struct { + Version uint8 + Flags uint32 + AtomPos +} + +func (a DataReferUrl) Tag() Tag { + return URL +} + +func (a DataReferUrl) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(URL)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a DataReferUrl) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + return +} + +func (a DataReferUrl) Len() (n int) { + n += 8 + n += 1 + n += 3 + return +} + +func (a *DataReferUrl) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + return +} + +func (a DataReferUrl) Children() (r []Atom) { + return +} + +const HDLR = Tag(0x68646c72) + +type HandlerRefer struct { + Version uint8 + Flags uint32 + Predefined uint32 + Type uint32 + Reserved [3]uint32 + Name string + AtomPos +} + +const ( + VideoHandler = 0x76696465 // vide + SoundHandler = 0x736f756e // soun +) + +func (a HandlerRefer) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(HDLR)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a HandlerRefer) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], a.Predefined) + n += 4 + pio.PutU32BE(b[n:], a.Type) + n += 4 + n += 3 * 4 + copy(b[n:], a.Name) + n += len(a.Name) + 1 + return +} + +func (a HandlerRefer) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 + n += 3 * 4 + n += len(a.Name) + 1 + return +} + +func (a *HandlerRefer) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("Predefined", n+offset, err) + return + } + a.Predefined = pio.U32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("Type", n+offset, err) + return + } + a.Type = pio.U32BE(b[n:]) + n += 4 + n += 3 * 4 + i := bytes.IndexByte(b[n:], 0) + if i > 0 { + a.Name = string(b[n : n+i]) + n += i + 1 + } + return +} + +func (a HandlerRefer) Children() (r []Atom) { + return +} + +func (a HandlerRefer) Tag() Tag { + return HDLR +} diff --git a/format/fmp4/fmp4io/sampleflags.go b/format/fmp4/fmp4io/sampleflags.go new file mode 100644 index 0000000..390cf87 --- /dev/null +++ b/format/fmp4/fmp4io/sampleflags.go @@ -0,0 +1,12 @@ +package fmp4io + +type SampleFlags uint32 + +// fragment sample flags +const ( + SampleIsNonSync SampleFlags = 0x00010000 + SampleHasDependencies SampleFlags = 0x01000000 + SampleNoDependencies SampleFlags = 0x02000000 + + SampleNonKeyframe = SampleHasDependencies | SampleIsNonSync +) diff --git a/format/fmp4/fmp4io/sampletable.go b/format/fmp4/fmp4io/sampletable.go new file mode 100644 index 0000000..4e63910 --- /dev/null +++ b/format/fmp4/fmp4io/sampletable.go @@ -0,0 +1,897 @@ +package fmp4io + +import ( + "fmt" + + "github.com/deepch/vdk/utils/bits/pio" +) + +const STBL = Tag(0x7374626c) + +type SampleTable struct { + SampleDesc *SampleDesc + TimeToSample *TimeToSample + CompositionOffset *CompositionOffset + SampleToChunk *SampleToChunk + SyncSample *SyncSample + ChunkOffset *ChunkOffset + SampleSize *SampleSize + AtomPos +} + +func (a SampleTable) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STBL)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a SampleTable) marshal(b []byte) (n int) { + if a.SampleDesc != nil { + n += a.SampleDesc.Marshal(b[n:]) + } + if a.TimeToSample != nil { + n += a.TimeToSample.Marshal(b[n:]) + } + if a.CompositionOffset != nil { + n += a.CompositionOffset.Marshal(b[n:]) + } + if a.SampleToChunk != nil { + n += a.SampleToChunk.Marshal(b[n:]) + } + if a.SyncSample != nil { + n += a.SyncSample.Marshal(b[n:]) + } + if a.SampleSize != nil { + n += a.SampleSize.Marshal(b[n:]) + } + if a.ChunkOffset != nil { + n += a.ChunkOffset.Marshal(b[n:]) + } + return +} + +func (a SampleTable) Len() (n int) { + n += 8 + if a.SampleDesc != nil { + n += a.SampleDesc.Len() + } + if a.TimeToSample != nil { + n += a.TimeToSample.Len() + } + if a.CompositionOffset != nil { + n += a.CompositionOffset.Len() + } + if a.SampleToChunk != nil { + n += a.SampleToChunk.Len() + } + if a.SyncSample != nil { + n += a.SyncSample.Len() + } + if a.ChunkOffset != nil { + n += a.ChunkOffset.Len() + } + if a.SampleSize != nil { + n += a.SampleSize.Len() + } + return +} + +func (a *SampleTable) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case STSD: + { + atom := &SampleDesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsd", n+offset, err) + return + } + a.SampleDesc = atom + } + case STTS: + { + atom := &TimeToSample{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stts", n+offset, err) + return + } + a.TimeToSample = atom + } + case CTTS: + { + atom := &CompositionOffset{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("ctts", n+offset, err) + return + } + a.CompositionOffset = atom + } + case STSC: + { + atom := &SampleToChunk{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsc", n+offset, err) + return + } + a.SampleToChunk = atom + } + case STSS: + { + atom := &SyncSample{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stss", n+offset, err) + return + } + a.SyncSample = atom + } + case STCO: + { + atom := &ChunkOffset{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stco", n+offset, err) + return + } + a.ChunkOffset = atom + } + case STSZ: + { + atom := &SampleSize{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("stsz", n+offset, err) + return + } + a.SampleSize = atom + } + } + n += size + } + return +} + +func (a SampleTable) Children() (r []Atom) { + if a.SampleDesc != nil { + r = append(r, a.SampleDesc) + } + if a.TimeToSample != nil { + r = append(r, a.TimeToSample) + } + if a.CompositionOffset != nil { + r = append(r, a.CompositionOffset) + } + if a.SampleToChunk != nil { + r = append(r, a.SampleToChunk) + } + if a.SyncSample != nil { + r = append(r, a.SyncSample) + } + if a.ChunkOffset != nil { + r = append(r, a.ChunkOffset) + } + if a.SampleSize != nil { + r = append(r, a.SampleSize) + } + return +} + +func (a SampleTable) Tag() Tag { + return STBL +} + +const STSD = Tag(0x73747364) + +type SampleDesc struct { + Version uint8 + AVC1Desc *AVC1Desc + MP4ADesc *MP4ADesc + OpusDesc *OpusSampleEntry + Unknowns []Atom + AtomPos +} + +func (a SampleDesc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSD)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a SampleDesc) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + n += 3 + _childrenNR := 0 + if a.AVC1Desc != nil { + _childrenNR++ + } + if a.MP4ADesc != nil { + _childrenNR++ + } + if a.OpusDesc != nil { + _childrenNR++ + } + _childrenNR += len(a.Unknowns) + pio.PutI32BE(b[n:], int32(_childrenNR)) + n += 4 + if a.AVC1Desc != nil { + n += a.AVC1Desc.Marshal(b[n:]) + } + if a.MP4ADesc != nil { + n += a.MP4ADesc.Marshal(b[n:]) + } + if a.OpusDesc != nil { + n += a.OpusDesc.Marshal(b[n:]) + } + for _, atom := range a.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} + +func (a SampleDesc) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if a.AVC1Desc != nil { + n += a.AVC1Desc.Len() + } + if a.MP4ADesc != nil { + n += a.MP4ADesc.Len() + } + if a.OpusDesc != nil { + n += a.OpusDesc.Len() + } + for _, atom := range a.Unknowns { + n += atom.Len() + } + return +} + +func (a *SampleDesc) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + n += 3 + n += 4 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case AVC1: + { + atom := &AVC1Desc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("avc1", n+offset, err) + return + } + a.AVC1Desc = atom + } + case MP4A: + { + atom := &MP4ADesc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("mp4a", n+offset, err) + return + } + a.MP4ADesc = atom + } + case OPUS: + { + atom := &OpusSampleEntry{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("OPUS", n+offset, err) + return + } + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + a.Unknowns = append(a.Unknowns, atom) + } + } + n += size + } + return +} + +func (a SampleDesc) Children() (r []Atom) { + if a.AVC1Desc != nil { + r = append(r, a.AVC1Desc) + } + if a.MP4ADesc != nil { + r = append(r, a.MP4ADesc) + } + if a.OpusDesc != nil { + r = append(r, a.OpusDesc) + } + r = append(r, a.Unknowns...) + return +} + +func (a SampleDesc) Tag() Tag { + return STSD +} + +const STTS = Tag(0x73747473) + +type TimeToSample struct { + Version uint8 + Flags uint32 + Entries []TimeToSampleEntry + AtomPos +} + +func (a TimeToSample) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STTS)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a TimeToSample) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(a.Entries))) + n += 4 + for _, entry := range a.Entries { + putTimeToSampleEntry(b[n:], entry) + n += lenTimeToSampleEntry + } + return +} + +func (a TimeToSample) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += lenTimeToSampleEntry * len(a.Entries) + return +} + +func (a *TimeToSample) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + a.Entries = make([]TimeToSampleEntry, _len_Entries) + if len(b) < n+lenTimeToSampleEntry*len(a.Entries) { + err = parseErr("TimeToSampleEntry", n+offset, err) + return + } + for i := range a.Entries { + a.Entries[i] = getTimeToSampleEntry(b[n:]) + n += lenTimeToSampleEntry + } + return +} + +func (a TimeToSample) Children() (r []Atom) { + return +} + +func (a TimeToSample) String() string { + return fmt.Sprintf("entries=%d", len(a.Entries)) +} + +type TimeToSampleEntry struct { + Count uint32 + Duration uint32 +} + +func getTimeToSampleEntry(b []byte) (a TimeToSampleEntry) { + a.Count = pio.U32BE(b[0:]) + a.Duration = pio.U32BE(b[4:]) + return +} + +func putTimeToSampleEntry(b []byte, a TimeToSampleEntry) { + pio.PutU32BE(b[0:], a.Count) + pio.PutU32BE(b[4:], a.Duration) +} + +const lenTimeToSampleEntry = 8 + +func (a TimeToSample) Tag() Tag { + return STTS +} + +const STSC = Tag(0x73747363) + +type SampleToChunk struct { + Version uint8 + Flags uint32 + Entries []SampleToChunkEntry + AtomPos +} + +func (a SampleToChunk) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSC)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a SampleToChunk) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(a.Entries))) + n += 4 + for _, entry := range a.Entries { + putSampleToChunkEntry(b[n:], entry) + n += lenSampleToChunkEntry + } + return +} + +func (a SampleToChunk) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += lenSampleToChunkEntry * len(a.Entries) + return +} + +func (a *SampleToChunk) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + a.Entries = make([]SampleToChunkEntry, _len_Entries) + if len(b) < n+lenSampleToChunkEntry*len(a.Entries) { + err = parseErr("SampleToChunkEntry", n+offset, err) + return + } + for i := range a.Entries { + a.Entries[i] = getSampleToChunkEntry(b[n:]) + n += lenSampleToChunkEntry + } + return +} + +func (a SampleToChunk) Children() (r []Atom) { + return +} + +func (a SampleToChunk) String() string { + return fmt.Sprintf("entries=%d", len(a.Entries)) +} + +type SampleToChunkEntry struct { + FirstChunk uint32 + SamplesPerChunk uint32 + SampleDescId uint32 +} + +func getSampleToChunkEntry(b []byte) (a SampleToChunkEntry) { + a.FirstChunk = pio.U32BE(b[0:]) + a.SamplesPerChunk = pio.U32BE(b[4:]) + a.SampleDescId = pio.U32BE(b[8:]) + return +} + +func putSampleToChunkEntry(b []byte, a SampleToChunkEntry) { + pio.PutU32BE(b[0:], a.FirstChunk) + pio.PutU32BE(b[4:], a.SamplesPerChunk) + pio.PutU32BE(b[8:], a.SampleDescId) +} + +const lenSampleToChunkEntry = 12 + +func (a SampleToChunk) Tag() Tag { + return STSC +} + +const CTTS = Tag(0x63747473) + +type CompositionOffset struct { + Version uint8 + Flags uint32 + Entries []CompositionOffsetEntry + AtomPos +} + +func (a CompositionOffset) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(CTTS)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a CompositionOffset) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(a.Entries))) + n += 4 + for _, entry := range a.Entries { + putCompositionOffsetEntry(b[n:], entry) + n += lenCompositionOffsetEntry + } + return +} + +func (a CompositionOffset) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += lenCompositionOffsetEntry * len(a.Entries) + return +} + +func (a *CompositionOffset) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + a.Entries = make([]CompositionOffsetEntry, _len_Entries) + if len(b) < n+lenCompositionOffsetEntry*len(a.Entries) { + err = parseErr("CompositionOffsetEntry", n+offset, err) + return + } + for i := range a.Entries { + a.Entries[i] = getCompositionOffsetEntry(b[n:]) + n += lenCompositionOffsetEntry + } + return +} + +func (a CompositionOffset) Children() (r []Atom) { + return +} + +func (a CompositionOffset) String() string { + return fmt.Sprintf("entries=%d", len(a.Entries)) +} + +type CompositionOffsetEntry struct { + Count uint32 + Offset uint32 +} + +func getCompositionOffsetEntry(b []byte) (a CompositionOffsetEntry) { + a.Count = pio.U32BE(b[0:]) + a.Offset = pio.U32BE(b[4:]) + return +} + +func putCompositionOffsetEntry(b []byte, a CompositionOffsetEntry) { + pio.PutU32BE(b[0:], a.Count) + pio.PutU32BE(b[4:], a.Offset) +} + +const lenCompositionOffsetEntry = 8 + +func (a CompositionOffset) Tag() Tag { + return CTTS +} + +const STSS = Tag(0x73747373) + +type SyncSample struct { + Version uint8 + Flags uint32 + Entries []uint32 + AtomPos +} + +func (a SyncSample) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSS)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a SyncSample) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(a.Entries))) + n += 4 + for _, entry := range a.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} + +func (a SyncSample) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 * len(a.Entries) + return +} + +func (a *SyncSample) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + a.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(a.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range a.Entries { + a.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} + +func (a SyncSample) Children() (r []Atom) { + return +} + +func (a SyncSample) Tag() Tag { + return STSS +} + +func (a SyncSample) String() string { + return fmt.Sprintf("entries=%d", len(a.Entries)) +} + +const STCO = Tag(0x7374636f) + +type ChunkOffset struct { + Version uint8 + Flags uint32 + Entries []uint32 + AtomPos +} + +func (a ChunkOffset) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STCO)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a ChunkOffset) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], uint32(len(a.Entries))) + n += 4 + for _, entry := range a.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} + +func (a ChunkOffset) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + n += 4 * len(a.Entries) + return +} + +func (a *ChunkOffset) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + a.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(a.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range a.Entries { + a.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} + +func (a ChunkOffset) Children() (r []Atom) { + return +} + +func (a ChunkOffset) Tag() Tag { + return STCO +} + +func (a ChunkOffset) String() string { + return fmt.Sprintf("entries=%d", len(a.Entries)) +} + +const STSZ = Tag(0x7374737a) + +type SampleSize struct { + Version uint8 + Flags uint32 + SampleSize uint32 + Entries []uint32 + AtomPos +} + +func (a SampleSize) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(STSZ)) + n += a.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} + +func (a SampleSize) marshal(b []byte) (n int) { + pio.PutU8(b[n:], a.Version) + n += 1 + pio.PutU24BE(b[n:], a.Flags) + n += 3 + pio.PutU32BE(b[n:], a.SampleSize) + n += 4 + if a.SampleSize != 0 { + return + } + pio.PutU32BE(b[n:], uint32(len(a.Entries))) + n += 4 + for _, entry := range a.Entries { + pio.PutU32BE(b[n:], entry) + n += 4 + } + return +} + +func (a SampleSize) Len() (n int) { + n += 8 + n += 1 + n += 3 + n += 4 + if a.SampleSize != 0 { + return + } + n += 4 + n += 4 * len(a.Entries) + return +} + +func (a *SampleSize) Unmarshal(b []byte, offset int) (n int, err error) { + (&a.AtomPos).setPos(offset, len(b)) + n += 8 + if len(b) < n+1 { + err = parseErr("Version", n+offset, err) + return + } + a.Version = pio.U8(b[n:]) + n += 1 + if len(b) < n+3 { + err = parseErr("Flags", n+offset, err) + return + } + a.Flags = pio.U24BE(b[n:]) + n += 3 + if len(b) < n+4 { + err = parseErr("SampleSize", n+offset, err) + return + } + a.SampleSize = pio.U32BE(b[n:]) + n += 4 + if a.SampleSize != 0 { + return + } + var _len_Entries uint32 + _len_Entries = pio.U32BE(b[n:]) + n += 4 + a.Entries = make([]uint32, _len_Entries) + if len(b) < n+4*len(a.Entries) { + err = parseErr("uint32", n+offset, err) + return + } + for i := range a.Entries { + a.Entries[i] = pio.U32BE(b[n:]) + n += 4 + } + return +} + +func (a SampleSize) Children() (r []Atom) { + return +} + +func (a SampleSize) Tag() Tag { + return STSZ +} + +func (a SampleSize) String() string { + return fmt.Sprintf("entries=%d", len(a.Entries)) +} diff --git a/format/fmp4/fmp4io/segindex.go b/format/fmp4/fmp4io/segindex.go new file mode 100644 index 0000000..c260701 --- /dev/null +++ b/format/fmp4/fmp4io/segindex.go @@ -0,0 +1,148 @@ +package fmp4io + +import "github.com/deepch/vdk/utils/bits/pio" + +const SIDX = Tag(0x73696478) + +type SegmentIndex struct { + FullAtom + ReferenceID uint32 + TimeScale uint32 + EarliestPTS uint64 + FirstOffset uint64 + References []SegmentReference +} + +type SegmentReference struct { + ReferencesBox bool + ReferencedSize uint32 + SubsegmentDuration uint32 + StartsWithSAP bool + SAPType uint8 + SAPDeltaTime uint32 +} + +func (s SegmentIndex) Tag() Tag { + return SIDX +} + +func (s SegmentIndex) Len() (n int) { + n = s.FullAtom.atomLen() + n += 4 + n += 4 + if s.Version == 0 { + n += 4 + n += 4 + } else { + n += 8 + n += 8 + } + n += 2 + n += 2 + n += 12 * len(s.References) + return +} + +func (s SegmentIndex) Marshal(b []byte) (n int) { + n = s.FullAtom.marshalAtom(b, SIDX) + pio.PutU32BE(b[n:], s.ReferenceID) + n += 4 + pio.PutU32BE(b[n:], s.TimeScale) + n += 4 + if s.Version == 0 { + pio.PutU32BE(b[n:], uint32(s.EarliestPTS)) + n += 4 + pio.PutU32BE(b[n:], uint32(s.FirstOffset)) + n += 4 + } else { + pio.PutU64BE(b[n:], s.EarliestPTS) + n += 8 + pio.PutU64BE(b[n:], s.FirstOffset) + n += 8 + } + n += 2 + pio.PutU16BE(b[n:], uint16(len(s.References))) + n += 2 + for _, ref := range s.References { + v := ref.ReferencedSize + if ref.ReferencesBox { + v |= 1 << 31 + } + pio.PutU32BE(b[n:], v) + n += 4 + pio.PutU32BE(b[n:], ref.SubsegmentDuration) + n += 4 + v = (uint32(ref.SAPType) << 28) | ref.SAPDeltaTime + if ref.StartsWithSAP { + v |= 1 << 31 + } + pio.PutU32BE(b[n:], v) + n += 4 + } + pio.PutU32BE(b, uint32(n)) + return +} + +func (s *SegmentIndex) Unmarshal(b []byte, offset int) (n int, err error) { + n, err = s.FullAtom.unmarshalAtom(b, offset) + if err != nil { + return + } + if len(b) < n+8 { + return 0, parseErr("ReferenceID", n+offset, nil) + } + s.ReferenceID = pio.U32BE(b[n:]) + n += 4 + s.TimeScale = pio.U32BE(b[n:]) + n += 4 + if s.Version == 0 { + if len(b) < n+8 { + return 0, parseErr("EarliestPTS", n+offset, nil) + } + s.EarliestPTS = uint64(pio.U32BE(b[n:])) + n += 4 + s.FirstOffset = uint64(pio.U32BE(b[n:])) + n += 4 + } else { + if len(b) < n+16 { + return 0, parseErr("EarliestPTS", n+offset, nil) + } + s.EarliestPTS = pio.U64BE(b[n:]) + n += 8 + s.FirstOffset = pio.U64BE(b[n:]) + n += 8 + } + if len(b) < n+4 { + return 0, parseErr("ReferenceCount", n+offset, nil) + } + n += 2 + refCount := int(pio.U16BE(b[n:])) + n += 2 + if len(b) < n+(12*refCount) { + return 0, parseErr("SegmentReference", n+offset, nil) + } + s.References = make([]SegmentReference, refCount) + for i := range s.References { + ref := &s.References[i] + refSize := pio.U32BE(b[n:]) + n += 4 + if refSize&(1<<31) != 0 { + ref.ReferencesBox = true + } + ref.ReferencedSize = refSize &^ ((1 << 31) - 1) + ref.SubsegmentDuration = pio.U32BE(b[n:]) + n += 4 + sapDelta := pio.U32BE(b[:n]) + n += 4 + if sapDelta&(1<<31) != 0 { + ref.StartsWithSAP = true + } + ref.SAPType = uint8(0x7 & (sapDelta >> 28)) + ref.SAPDeltaTime = sapDelta &^ ((1 << 28) - 1) + } + return +} + +func (s SegmentIndex) Children() []Atom { + return nil +} diff --git a/format/fmp4/fragment/fragment.go b/format/fmp4/fragment/fragment.go new file mode 100644 index 0000000..5782fbe --- /dev/null +++ b/format/fmp4/fragment/fragment.go @@ -0,0 +1,23 @@ +package fragment + +import ( + "time" + + "github.com/deepch/vdk/av" +) + +type Fragment struct { + Bytes []byte + Length int + Independent bool + Duration time.Duration +} + +type Fragmenter interface { + av.PacketWriter + Fragment() (Fragment, error) + Duration() time.Duration + TimeScale() uint32 + MovieHeader() (filename, contentType string, contents []byte) + NewSegment() +} diff --git a/format/fmp4/hlsfrag.go b/format/fmp4/hlsfrag.go new file mode 100644 index 0000000..e18b800 --- /dev/null +++ b/format/fmp4/hlsfrag.go @@ -0,0 +1,100 @@ +package fmp4 + +import ( + "errors" + "fmt" + "sync" + "time" + + "github.com/deepch/vdk/av" + "github.com/deepch/vdk/format/fmp4/fmp4io" + "github.com/deepch/vdk/format/fmp4/fragment" +) + +var ( + shdrOnce sync.Once + shdr []byte +) + +// MovieFragmenter breaks a stream into segments each containing both tracks from the original stream +type MovieFragmenter struct { + tracks []*TrackFragmenter + fhdr []byte + vidx int + seqNum uint32 + shdrw bool +} + +// NewMovie creates a movie fragmenter from a stream +func NewMovie(streams []av.CodecData) (*MovieFragmenter, error) { + f := &MovieFragmenter{ + tracks: make([]*TrackFragmenter, len(streams)), + vidx: -1, + } + atoms := make([]*fmp4io.Track, len(streams)) + var err error + for i, cd := range streams { + f.tracks[i], err = NewTrack(cd) + if err != nil { + return nil, fmt.Errorf("track %d: %w", i, err) + } + atoms[i] = f.tracks[i].atom + if cd.Type().IsVideo() { + f.vidx = i + } + } + if f.vidx < 0 { + return nil, errors.New("no video track found") + } + f.fhdr, err = MovieHeader(atoms) + if err != nil { + return nil, err + } + return f, err +} + +// Fragment produces a fragment out of the currently-queued packets. +func (f *MovieFragmenter) Fragment() (fragment.Fragment, error) { + dur := f.tracks[f.vidx].Duration() + var tracks []fragmentWithData + for _, track := range f.tracks { + tf := track.makeFragment() + if tf.trackFrag != nil { + tracks = append(tracks, tf) + } + } + if len(tracks) == 0 { + return fragment.Fragment{}, nil + } + f.seqNum++ + initial := !f.shdrw + f.shdrw = true + frag := marshalFragment(tracks, f.seqNum, initial) + frag.Duration = dur + return frag, nil +} + +// WritePacket formats and queues a packet for the next fragment to be written +func (f *MovieFragmenter) WritePacket(pkt av.Packet) error { + return f.tracks[pkt.Idx].WritePacket(pkt) +} + +// Duration calculates the elapsed duration between the first and last pending video frame +func (f *MovieFragmenter) Duration() time.Duration { + return f.tracks[f.vidx].Duration() +} + +// MovieHeader marshals an init.mp4 for the fragmenter's tracks +func (f *MovieFragmenter) MovieHeader() (filename, contentType string, blob []byte) { + return "init.mp4", "video/mp4", f.fhdr +} + +// NewSegment indicates that a new segment has begun and the next call to +// Fragment() should include a leading FTYP header +func (f *MovieFragmenter) NewSegment() { + f.shdrw = false +} + +func (f *MovieFragmenter) TimeScale() uint32 { + return 90000 +} diff --git a/format/fmp4/makefragment.go b/format/fmp4/makefragment.go new file mode 100644 index 0000000..f3953df --- /dev/null +++ b/format/fmp4/makefragment.go @@ -0,0 +1,178 @@ +package fmp4 + +import ( + "github.com/deepch/vdk/av" + "github.com/deepch/vdk/format/fmp4/fmp4io" + "github.com/deepch/vdk/format/fmp4/fragment" + "github.com/deepch/vdk/format/fmp4/timescale" + "github.com/deepch/vdk/utils/bits/pio" +) + +type fragmentWithData struct { + trackFrag *fmp4io.TrackFrag + packets []av.Packet + independent bool +} + +func (f *TrackFragmenter) makeFragment() fragmentWithData { + if len(f.pending) < 2 { + return fragmentWithData{} + } + entryCount := len(f.pending) - 1 + // timescale for first packet + startTime := f.pending[0].Time + startDTS := timescale.ToScale(startTime, f.timeScale) + // build fragment metadata + defaultFlags := fmp4io.SampleNoDependencies + if f.codecData.Type().IsVideo() { + defaultFlags = fmp4io.SampleNonKeyframe + } + track := &fmp4io.TrackFrag{ + Header: &fmp4io.TrackFragHeader{ + Flags: fmp4io.TrackFragDefaultBaseIsMOOF, + TrackID: f.trackID, + }, + DecodeTime: &fmp4io.TrackFragDecodeTime{ + Version: 1, + Time: startDTS, + }, + Run: &fmp4io.TrackFragRun{ + Flags: fmp4io.TrackRunDataOffset, + Entries: make([]fmp4io.TrackFragRunEntry, entryCount), + }, + } + // add samples to the fragment run + curDTS := startDTS + for i, pkt := range f.pending[:entryCount] { + // calculate the absolute DTS of the next sample and use the difference as the duration + nextTime := f.pending[i+1].Time + nextDTS := timescale.ToScale(nextTime, f.timeScale) + entry := fmp4io.TrackFragRunEntry{ + Duration: uint32(nextDTS - curDTS), + Flags: defaultFlags, + Size: uint32(len(pkt.Data)), + } + if pkt.IsKeyFrame { + entry.Flags = fmp4io.SampleNoDependencies + } + if i == 0 { + // Optimistically use the first sample's fields as defaults. + // If a later sample has different values, then the default will be cleared and per-sample values will be used for that field. + track.Header.DefaultDuration = entry.Duration + track.Header.DefaultSize = entry.Size + track.Header.DefaultFlags = entry.Flags + track.Run.FirstSampleFlags = entry.Flags + } else { + if entry.Duration != track.Header.DefaultDuration { + track.Header.DefaultDuration = 0 + } + if entry.Size != track.Header.DefaultSize { + track.Header.DefaultSize = 0 + } + // The first sample's flags can be specified separately if all other samples have the same flags. + // Thus the default flags come from the second sample. + if i == 1 { + track.Header.DefaultFlags = entry.Flags + } else if entry.Flags != track.Header.DefaultFlags { + track.Header.DefaultFlags = 0 + } + } + if pkt.CompositionTime != 0 { + // add composition time to entries in this run + track.Run.Flags |= fmp4io.TrackRunSampleCTS + relCTS := timescale.Relative(pkt.CompositionTime, f.timeScale) + if relCTS < 0 { + // negative composition time needs version 1 + track.Run.Version = 1 + } + entry.CTS = relCTS + } + // log.Printf("%3d %d -> %d = %d %s -> %s = %s comp %s %d", f.trackID, curDTS, nextDTS, nextDTS-curDTS, pkt.Time, nextTime, nextTime-pkt.Time, pkt.CompositionTime, entry.CTS) + curDTS = nextDTS + track.Run.Entries[i] = entry + } + if track.Header.DefaultSize != 0 { + // all samples are the same size + track.Header.Flags |= fmp4io.TrackFragDefaultSize + } else { + track.Run.Flags |= fmp4io.TrackRunSampleSize + } + if track.Header.DefaultDuration != 0 { + // all samples are the same duration + track.Header.Flags |= fmp4io.TrackFragDefaultDuration + } else { + track.Run.Flags |= fmp4io.TrackRunSampleDuration + } + if track.Header.DefaultFlags != 0 { + // all samples are the same duration + track.Header.Flags |= fmp4io.TrackFragDefaultFlags + if track.Run.FirstSampleFlags != track.Header.DefaultFlags { + // except the first one + track.Run.Flags |= fmp4io.TrackRunFirstSampleFlags + } + } else { + track.Run.Flags |= fmp4io.TrackRunSampleFlags + } + d := fragmentWithData{ + trackFrag: track, + packets: f.pending[:entryCount], + independent: track.Run.FirstSampleFlags&fmp4io.SampleNoDependencies != 0, + } + f.pending = []av.Packet{f.pending[entryCount]} + return d +} + +func marshalFragment(tracks []fragmentWithData, seqNum uint32, initial bool) fragment.Fragment { + // fill out fragment header + moof := &fmp4io.MovieFrag{ + Header: &fmp4io.MovieFragHeader{ + Seqnum: seqNum, + }, + Tracks: make([]*fmp4io.TrackFrag, len(tracks)), + } + independent := true + for i, track := range tracks { + moof.Tracks[i] = track.trackFrag + if !track.independent { + independent = false + } + } + // calculate track data offsets relative to the start of the MOOF + dataBase := moof.Len() + 8 // MOOF plus the MDAT header + dataOffset := dataBase + for i, track := range tracks { + moof.Tracks[i].Run.DataOffset = uint32(dataOffset) + for _, pkt := range track.packets { + dataOffset += len(pkt.Data) + } + } + // marshal MOOF and MDAT header + var shdrSize int + if initial { + shdrOnce.Do(func() { + shdr = FragmentHeader() + }) + shdrSize = len(shdr) + } + b := make([]byte, shdrSize+dataBase, shdrSize+dataOffset) + var n int + if initial { + copy(b, shdr) + n = len(shdr) + } + n += moof.Marshal(b[n:]) + pio.PutU32BE(b[n:], uint32(dataOffset-dataBase+8)) + pio.PutU32BE(b[n+4:], uint32(fmp4io.MDAT)) + // write MDAT contents + for i, track := range tracks { + moof.Tracks[i].Run.DataOffset = uint32(dataOffset) + for _, pkt := range track.packets { + b = append(b, pkt.Data...) + } + } + return fragment.Fragment{ + Bytes: b, + Length: len(b), + Independent: independent, + } +} diff --git a/format/fmp4/streamatoms.go b/format/fmp4/streamatoms.go new file mode 100644 index 0000000..8e685be --- /dev/null +++ b/format/fmp4/streamatoms.go @@ -0,0 +1,162 @@ +package fmp4 + +import ( + "fmt" + + "github.com/deepch/vdk/av" + "github.com/deepch/vdk/codec/aacparser" + "github.com/deepch/vdk/codec/h264parser" + "github.com/deepch/vdk/codec/opusparser" + "github.com/deepch/vdk/format/fmp4/esio" + "github.com/deepch/vdk/format/fmp4/fmp4io" +) + +// Track creates a TRAK atom for this stream +func (f *TrackFragmenter) Track() (*fmp4io.Track, error) { + sample := &fmp4io.SampleTable{ + SampleDesc: &fmp4io.SampleDesc{}, + TimeToSample: &fmp4io.TimeToSample{}, + SampleToChunk: &fmp4io.SampleToChunk{}, + SampleSize: &fmp4io.SampleSize{}, + ChunkOffset: &fmp4io.ChunkOffset{}, + } + switch cd := f.codecData.(type) { + case h264parser.CodecData: + f.timeScale = 90000 + cd.RecordInfo.LengthSizeMinusOne = 3 + conf := make([]byte, cd.RecordInfo.Len()) + cd.RecordInfo.Marshal(conf) + sample.SampleDesc.AVC1Desc = &fmp4io.AVC1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: int16(cd.Width()), + Height: int16(cd.Height()), + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + Conf: &fmp4io.AVC1Conf{Data: conf}, + } + case aacparser.CodecData: + f.timeScale = 48000 + dc, err := esio.DecoderConfigFromCodecData(cd) + if err != nil { + return nil, fmt.Errorf("decoding AAC configuration: %w", err) + } + sample.SampleDesc.MP4ADesc = &fmp4io.MP4ADesc{ + DataRefIdx: 1, + NumberOfChannels: int16(cd.ChannelLayout().Count()), + SampleSize: 16, + SampleRate: float64(cd.SampleRate()), + Conf: &fmp4io.ElemStreamDesc{ + StreamDescriptor: &esio.StreamDescriptor{ + ESID: uint16(f.trackID), + DecoderConfig: dc, + SLConfig: &esio.SLConfigDescriptor{Predefined: esio.SLConfigMP4}, + }, + }, + } + case *opusparser.CodecData: + f.timeScale = 48000 + sample.SampleDesc.OpusDesc = &fmp4io.OpusSampleEntry{ + DataRefIdx: 1, + NumberOfChannels: uint16(cd.ChannelLayout().Count()), + SampleSize: 16, + SampleRate: float64(cd.SampleRate()), + Conf: &fmp4io.OpusSpecificConfiguration{ + OutputChannelCount: uint8(cd.ChannelLayout().Count()), + PreSkip: 3840, // 80ms + }, + } + default: + return nil, fmt.Errorf("mp4: codec type=%v is not supported", f.codecData.Type()) + } + trackAtom := &fmp4io.Track{ + Header: &fmp4io.TrackHeader{ + Flags: 0x0003, // Track enabled | Track in movie + Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + TrackID: f.trackID, + }, + Media: &fmp4io.Media{ + Header: &fmp4io.MediaHeader{ + Language: 21956, + TimeScale: f.timeScale, + }, + Info: &fmp4io.MediaInfo{ + Sample: sample, + Data: &fmp4io.DataInfo{ + Refer: &fmp4io.DataRefer{ + Url: &fmp4io.DataReferUrl{ + Flags: 0x000001, // Self reference + }, + }, + }, + }, + }, + } + if f.codecData.Type().IsVideo() { + vc := f.codecData.(av.VideoCodecData) + trackAtom.Media.Handler = &fmp4io.HandlerRefer{ + Type: fmp4io.VideoHandler, + Name: "VideoHandler", + } + trackAtom.Media.Info.Video = &fmp4io.VideoMediaInfo{ + Flags: 0x000001, + } + trackAtom.Header.TrackWidth = float64(vc.Width()) + trackAtom.Header.TrackHeight = float64(vc.Height()) + } else { + trackAtom.Header.Volume = 1 + trackAtom.Header.AlternateGroup = 1 + trackAtom.Media.Handler = &fmp4io.HandlerRefer{ + Type: fmp4io.SoundHandler, + Name: "SoundHandler", + } + trackAtom.Media.Info.Sound = &fmp4io.SoundMediaInfo{} + } + return trackAtom, nil +} + +// MovieHeader marshals an init.mp4 for the given tracks +func MovieHeader(tracks []*fmp4io.Track) ([]byte, error) { + ftyp := fmp4io.FileType{ + MajorBrand: 0x69736f36, // iso6 + CompatibleBrands: []uint32{ + 0x69736f35, // iso5 + 0x6d703431, // mp41 + }, + } + moov := &fmp4io.Movie{ + Header: &fmp4io.MovieHeader{ + PreferredRate: 1, + PreferredVolume: 1, + Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + TimeScale: 1000, + }, + Tracks: tracks, + MovieExtend: &fmp4io.MovieExtend{}, + } + for _, track := range tracks { + if track.Header.TrackID >= moov.Header.NextTrackID { + moov.Header.NextTrackID = track.Header.TrackID + 1 + } + moov.MovieExtend.Tracks = append(moov.MovieExtend.Tracks, + &fmp4io.TrackExtend{TrackID: track.Header.TrackID, DefaultSampleDescIdx: 1}) + } + // marshal init segment + fhdr := make([]byte, ftyp.Len()+moov.Len()) + n := ftyp.Marshal(fhdr) + moov.Marshal(fhdr[n:]) + return fhdr, nil +} + +// FragmentHeader returns the header needed for the beginning of a MP4 segment file +func FragmentHeader() []byte { + styp := fmp4io.SegmentType{ + MajorBrand: 0x6d736468, // msdh + CompatibleBrands: []uint32{0x6d736978}, // msix + } + shdr := make([]byte, styp.Len()) + styp.Marshal(shdr) + return shdr +} diff --git a/format/fmp4/timescale/timescale.go b/format/fmp4/timescale/timescale.go new file mode 100644 index 0000000..f30c475 --- /dev/null +++ b/format/fmp4/timescale/timescale.go @@ -0,0 +1,27 @@ +package timescale + +import ( + "math/bits" + "time" +) + +// ToScale converts a decode time from time.Duration to a specified timescale +func ToScale(t time.Duration, scale uint32) uint64 { + hi, lo := bits.Mul64(uint64(t), uint64(scale)) + dts, rem := bits.Div64(hi, lo, uint64(time.Second)) + if rem >= uint64(time.Second/2) { + // round up + dts++ + } + return dts +} + +// Relative converts a sub-second relative time (which may be negative) to a specified timescale +func Relative(t time.Duration, scale uint32) int32 { + rel := int64(t) * int64(scale) / int64(time.Second/2) + if (rel&1 != 0) == (t > 0) { + // round up + rel++ + } + return int32(rel >> 1) +} diff --git a/format/fmp4/timescale/timescale_test.go b/format/fmp4/timescale/timescale_test.go new file mode 100644 index 0000000..6ecbf3d --- /dev/null +++ b/format/fmp4/timescale/timescale_test.go @@ -0,0 +1,60 @@ +package timescale + +import ( + "testing" + "time" +) + +func TestToScale(t *testing.T) { + const scale uint32 = 90000 + values := []struct { + T time.Duration + V uint64 + }{ + {0, 0}, + {time.Second/60 - 1, 1500}, + {time.Second/60 + 0, 1500}, + {time.Second/60 + 1, 1500}, + {(time.Second/60)*60 - 1, 90000}, + {(time.Second/60)*60 + 0, 90000}, + {(time.Second/60)*60 + 1, 90000}, + {time.Second * (1 << 32), 90000 * (1 << 32)}, + {time.Second*(1<<32) + time.Second/60 - 1, 90000*(1<<32) + 1500}, + {time.Second*(1<<32) + time.Second/60 + 0, 90000*(1<<32) + 1500}, + {time.Second*(1<<32) + time.Second/60 + 1, 90000*(1<<32) + 1500}, + } + for _, ex := range values { + n := ToScale(ex.T, scale) + if n != ex.V { + t.Errorf("%d (%s): expected %d, got %d", ex.T, ex.T, ex.V, n) + } + } +} + +func TestRelative(t *testing.T) { + const scale uint32 = 90000 + values := []struct { + T time.Duration + V int32 + }{ + {0, 0}, + {time.Second/60 - 1, 1500}, + {time.Second/60 + 0, 1500}, + {time.Second/60 + 1, 1500}, + {(time.Second/60)*5 - 1, 7500}, + {(time.Second/60)*5 + 0, 7500}, + {(time.Second/60)*5 + 1, 7500}, + {-time.Second/60 - 1, -1500}, + {-time.Second/60 + 0, -1500}, + {-time.Second/60 + 1, -1500}, + {(-time.Second/60)*5 - 1, -7500}, + {(-time.Second/60)*5 + 0, -7500}, + {(-time.Second/60)*5 + 1, -7500}, + } + for _, ex := range values { + n := Relative(ex.T, scale) + if n != ex.V { + t.Errorf("%d (%s): expected %d, got %d", ex.T, ex.T, ex.V, n) + } + } +} diff --git a/format/fmp4/trackfrag.go b/format/fmp4/trackfrag.go new file mode 100644 index 0000000..4aa5cc3 --- /dev/null +++ b/format/fmp4/trackfrag.go @@ -0,0 +1,105 @@ +package fmp4 + +import ( + "time" + + "github.com/deepch/vdk/av" + "github.com/deepch/vdk/codec/h264parser" + "github.com/deepch/vdk/format/fmp4/fmp4io" + "github.com/deepch/vdk/format/fmp4/fragment" +) + +// TrackFragmenter writes a single audio or video stream as a series of CMAF (fMP4) fragments +type TrackFragmenter struct { + codecData av.CodecData + trackID uint32 + timeScale uint32 + atom *fmp4io.Track + pending []av.Packet + + // for CMAF (single track) only + seqNum uint32 + fhdr []byte + shdrw bool +} + +// NewTrack creates a fragmenter from the given stream codec +func NewTrack(codecData av.CodecData) (*TrackFragmenter, error) { + var trackID uint32 = 1 + if codecData.Type().IsVideo() { + trackID = 2 + } + f := &TrackFragmenter{ + codecData: codecData, + trackID: trackID, + } + var err error + f.atom, err = f.Track() + if err != nil { + return nil, err + } + f.fhdr, err = MovieHeader([]*fmp4io.Track{f.atom}) + return f, err +} + +// WritePacket appends a packet to the fragmenter +func (f *TrackFragmenter) WritePacket(pkt av.Packet) error { + switch f.codecData.(type) { + case h264parser.CodecData: + // reformat NALUs as AVCC + nalus, typ := h264parser.SplitNALUs(pkt.Data) + if typ == h264parser.NALU_AVCC { + // already there + break + } + b := make([]byte, 0, len(pkt.Data)+3*len(nalus)) + for _, nalu := range nalus { + j := len(nalu) + b = append(b, byte(j>>24), byte(j>>16), byte(j>>8), byte(j)) + b = append(b, nalu...) + } + pkt.Data = b + } + f.pending = append(f.pending, pkt) + return nil +} + +// Duration calculates the elapsed duration between the first and last pending packet in the fragmenter +func (f *TrackFragmenter) Duration() time.Duration { + if len(f.pending) < 2 { + return 0 + } + return f.pending[len(f.pending)-1].Time - f.pending[0].Time +} + +// TimeScale returns the number of timestamp ticks (DTS) that elapse in 1 second for this track +func (f *TrackFragmenter) TimeScale() uint32 { + return f.timeScale +} + +// Fragment produces a fragment out of the currently-queued packets. +func (f *TrackFragmenter) Fragment() (fragment.Fragment, error) { + dur := f.Duration() + tf := f.makeFragment() + if tf.trackFrag == nil { + // not enough packets received + return fragment.Fragment{}, nil + } + f.seqNum++ + initial := !f.shdrw + f.shdrw = true + frag := marshalFragment([]fragmentWithData{tf}, f.seqNum, initial) + frag.Duration = dur + return frag, nil +} + +// NewSegment indicates that a new segment has begun and the next call to +// Fragment() should include a leading FTYP header. +func (f *TrackFragmenter) NewSegment() { + f.shdrw = false +} + +// MovieHeader marshals an init.mp4 for this track +func (f *TrackFragmenter) MovieHeader() (filename, contentType string, blob []byte) { + return "init.mp4", "video/mp4", f.fhdr +} diff --git a/format/nvr/demuxer.go b/format/nvr/demuxer.go new file mode 100644 index 0000000..b3702c8 --- /dev/null +++ b/format/nvr/demuxer.go @@ -0,0 +1,24 @@ +package nvr + +type DeMuxer struct { +} + +//NewDeMuxer func +func NewDeMuxer() *DeMuxer { + return &DeMuxer{} +} + +//ReadIndex func +func (obj *DeMuxer) ReadIndex() (err error) { + return nil +} + +//ReadRange func +func (obj *DeMuxer) ReadRange() (err error) { + return nil +} + +//ReadGop func +func (obj *DeMuxer) ReadGop() (err error) { + return nil +} diff --git a/format/nvr/muxer.go b/format/nvr/muxer.go new file mode 100644 index 0000000..7dbc02b --- /dev/null +++ b/format/nvr/muxer.go @@ -0,0 +1,59 @@ +package nvr + +import ( + "log" + "os" + "time" + + "github.com/deepch/vdk/av" +) + +type Muxer struct { + name string + patch string + started bool + file *os.File + codec []av.CodecData + buffer []*av.Packet + bufferDur time.Duration + seqDur time.Duration +} + +//NewMuxer func +func NewMuxer(codec []av.CodecData, name, patch string, seqDur time.Duration) *Muxer { + return &Muxer{ + codec: codec, + name: name, + patch: patch, + seqDur: seqDur, + } +} + +//WritePacket func +func (obj *Muxer) CodecUpdate(val []av.CodecData) { + obj.codec = val +} + +//WritePacket func +func (obj *Muxer) WritePacket(pkt *av.Packet) (err error) { + if !obj.started && pkt.IsKeyFrame { + obj.started = true + } + if obj.started { + if pkt.IsKeyFrame && obj.bufferDur >= obj.seqDur { + log.Println("write to drive", len(obj.buffer), obj.bufferDur) + obj.buffer = nil + obj.bufferDur = 0 + } + obj.buffer = append(obj.buffer, pkt) + if pkt.Idx == 0 { + obj.bufferDur += pkt.Duration + } + } + return nil +} + +//Close func +func (obj *Muxer) Close() { + return +}