test hls ll
This commit is contained in:
		
							
								
								
									
										124
									
								
								codec/opusparser/opusparser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								codec/opusparser/opusparser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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, | ||||
| } | ||||
							
								
								
									
										98
									
								
								format/fmp4/esio/builder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								format/fmp4/esio/builder.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										95
									
								
								format/fmp4/esio/decoderconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								format/fmp4/esio/decoderconf.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										162
									
								
								format/fmp4/esio/esio.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								format/fmp4/esio/esio.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										43
									
								
								format/fmp4/esio/slconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								format/fmp4/esio/slconf.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										204
									
								
								format/fmp4/fmp4io/atom.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								format/fmp4/fmp4io/atom.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										346
									
								
								format/fmp4/fmp4io/avc1.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										346
									
								
								format/fmp4/fmp4io/avc1.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										37
									
								
								format/fmp4/fmp4io/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								format/fmp4/fmp4io/error.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										192
									
								
								format/fmp4/fmp4io/extend.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								format/fmp4/fmp4io/extend.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										103
									
								
								format/fmp4/fmp4io/filetype.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								format/fmp4/fmp4io/filetype.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										765
									
								
								format/fmp4/fmp4io/fragment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										765
									
								
								format/fmp4/fmp4io/fragment.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										64
									
								
								format/fmp4/fmp4io/marshal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								format/fmp4/fmp4io/marshal.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										616
									
								
								format/fmp4/fmp4io/media.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										616
									
								
								format/fmp4/fmp4io/media.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										564
									
								
								format/fmp4/fmp4io/movie.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										564
									
								
								format/fmp4/fmp4io/movie.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
							
								
								
									
										222
									
								
								format/fmp4/fmp4io/mp4a.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								format/fmp4/fmp4io/mp4a.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										202
									
								
								format/fmp4/fmp4io/opus.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								format/fmp4/fmp4io/opus.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										251
									
								
								format/fmp4/fmp4io/refer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								format/fmp4/fmp4io/refer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										12
									
								
								format/fmp4/fmp4io/sampleflags.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								format/fmp4/fmp4io/sampleflags.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| ) | ||||
							
								
								
									
										897
									
								
								format/fmp4/fmp4io/sampletable.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										897
									
								
								format/fmp4/fmp4io/sampletable.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										148
									
								
								format/fmp4/fmp4io/segindex.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								format/fmp4/fmp4io/segindex.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										23
									
								
								format/fmp4/fragment/fragment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								format/fmp4/fragment/fragment.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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() | ||||
| } | ||||
							
								
								
									
										100
									
								
								format/fmp4/hlsfrag.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								format/fmp4/hlsfrag.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										178
									
								
								format/fmp4/makefragment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								format/fmp4/makefragment.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										162
									
								
								format/fmp4/streamatoms.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								format/fmp4/streamatoms.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										27
									
								
								format/fmp4/timescale/timescale.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								format/fmp4/timescale/timescale.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										60
									
								
								format/fmp4/timescale/timescale_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								format/fmp4/timescale/timescale_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										105
									
								
								format/fmp4/trackfrag.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								format/fmp4/trackfrag.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										24
									
								
								format/nvr/demuxer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								format/nvr/demuxer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										59
									
								
								format/nvr/muxer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								format/nvr/muxer.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Andrey Semochkin
					Andrey Semochkin