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