test hls ll
This commit is contained in:
parent
be0abf0857
commit
2eeda69894
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user