You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
503 lines
10 KiB
Go
503 lines
10 KiB
Go
package mp4io
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitlab.com/ics_cinnamon/joy4/utils/bits/pio"
|
|
)
|
|
|
|
type ParseError struct {
|
|
Debug string
|
|
Offset int
|
|
prev *ParseError
|
|
}
|
|
|
|
func (self *ParseError) Error() string {
|
|
s := []string{}
|
|
for p := self; p != nil; p = p.prev {
|
|
s = append(s, fmt.Sprintf("%s:%d", p.Debug, p.Offset))
|
|
}
|
|
return "mp4io: parse error: " + strings.Join(s, ",")
|
|
}
|
|
|
|
func parseErr(debug string, offset int, prev error) (err error) {
|
|
_prev, _ := prev.(*ParseError)
|
|
return &ParseError{Debug: debug, Offset: offset, prev: _prev}
|
|
}
|
|
|
|
func GetTime32(b []byte) (t time.Time) {
|
|
sec := pio.U32BE(b)
|
|
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) {
|
|
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)
|
|
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) {
|
|
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
|
|
}
|
|
|
|
type Tag uint32
|
|
|
|
func (self Tag) String() string {
|
|
var b [4]byte
|
|
pio.PutU32BE(b[:], uint32(self))
|
|
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 (self AtomPos) Pos() (int, int) {
|
|
return self.Offset, self.Size
|
|
}
|
|
|
|
func (self *AtomPos) setPos(offset int, size int) {
|
|
self.Offset, self.Size = offset, size
|
|
}
|
|
|
|
type Dummy struct {
|
|
Data []byte
|
|
Tag_ Tag
|
|
AtomPos
|
|
}
|
|
|
|
func (self Dummy) Children() []Atom {
|
|
return nil
|
|
}
|
|
|
|
func (self Dummy) Tag() Tag {
|
|
return self.Tag_
|
|
}
|
|
|
|
func (self Dummy) Len() int {
|
|
return len(self.Data)
|
|
}
|
|
|
|
func (self Dummy) Marshal(b []byte) int {
|
|
copy(b, self.Data)
|
|
return len(self.Data)
|
|
}
|
|
|
|
func (self *Dummy) Unmarshal(b []byte, offset int) (n int, err error) {
|
|
(&self.AtomPos).setPos(offset, len(b))
|
|
self.Data = b
|
|
n = len(b)
|
|
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
|
|
}
|
|
|
|
const (
|
|
TFHD_BASE_DATA_OFFSET = 0x01
|
|
TFHD_STSD_ID = 0x02
|
|
TFHD_DEFAULT_DURATION = 0x08
|
|
TFHD_DEFAULT_SIZE = 0x10
|
|
TFHD_DEFAULT_FLAGS = 0x20
|
|
TFHD_DURATION_IS_EMPTY = 0x010000
|
|
TFHD_DEFAULT_BASE_IS_MOOF = 0x020000
|
|
)
|
|
|
|
const (
|
|
TRUN_DATA_OFFSET = 0x01
|
|
TRUN_FIRST_SAMPLE_FLAGS = 0x04
|
|
TRUN_SAMPLE_DURATION = 0x100
|
|
TRUN_SAMPLE_SIZE = 0x200
|
|
TRUN_SAMPLE_FLAGS = 0x400
|
|
TRUN_SAMPLE_CTS = 0x800
|
|
)
|
|
|
|
const (
|
|
MP4ESDescrTag = 3
|
|
MP4DecConfigDescrTag = 4
|
|
MP4DecSpecificDescrTag = 5
|
|
)
|
|
|
|
type ElemStreamDesc struct {
|
|
DecConfig []byte
|
|
TrackId uint16
|
|
AtomPos
|
|
}
|
|
|
|
func (self ElemStreamDesc) Children() []Atom {
|
|
return nil
|
|
}
|
|
|
|
func (self ElemStreamDesc) fillLength(b []byte, length int) (n int) {
|
|
for i := 3; i > 0; i-- {
|
|
b[n] = uint8(length>>uint(7*i))&0x7f | 0x80
|
|
n++
|
|
}
|
|
b[n] = uint8(length & 0x7f)
|
|
n++
|
|
return
|
|
}
|
|
|
|
func (self ElemStreamDesc) lenDescHdr() (n int) {
|
|
return 5
|
|
}
|
|
|
|
func (self ElemStreamDesc) fillDescHdr(b []byte, tag uint8, datalen int) (n int) {
|
|
b[n] = tag
|
|
n++
|
|
n += self.fillLength(b[n:], datalen)
|
|
return
|
|
}
|
|
|
|
func (self ElemStreamDesc) lenESDescHdr() (n int) {
|
|
return self.lenDescHdr() + 3
|
|
}
|
|
|
|
func (self ElemStreamDesc) fillESDescHdr(b []byte, datalen int) (n int) {
|
|
n += self.fillDescHdr(b[n:], MP4ESDescrTag, datalen)
|
|
pio.PutU16BE(b[n:], self.TrackId)
|
|
n += 2
|
|
b[n] = 0 // flags
|
|
n++
|
|
return
|
|
}
|
|
|
|
func (self ElemStreamDesc) lenDecConfigDescHdr() (n int) {
|
|
return self.lenDescHdr() + 2 + 3 + 4 + 4 + self.lenDescHdr()
|
|
}
|
|
|
|
func (self ElemStreamDesc) fillDecConfigDescHdr(b []byte, datalen int) (n int) {
|
|
n += self.fillDescHdr(b[n:], MP4DecConfigDescrTag, datalen)
|
|
b[n] = 0x40 // objectid
|
|
n++
|
|
b[n] = 0x15 // streamtype
|
|
n++
|
|
// buffer size db
|
|
pio.PutU24BE(b[n:], 0)
|
|
n += 3
|
|
// max bitrage
|
|
pio.PutU32BE(b[n:], uint32(200000))
|
|
n += 4
|
|
// avg bitrage
|
|
pio.PutU32BE(b[n:], uint32(0))
|
|
n += 4
|
|
n += self.fillDescHdr(b[n:], MP4DecSpecificDescrTag, datalen-n)
|
|
return
|
|
}
|
|
|
|
func (self ElemStreamDesc) Len() (n int) {
|
|
return 8 + 4 + self.lenESDescHdr() + self.lenDecConfigDescHdr() + len(self.DecConfig) + self.lenDescHdr() + 1
|
|
}
|
|
|
|
// Version(4)
|
|
// ESDesc(
|
|
// MP4ESDescrTag
|
|
// ESID(2)
|
|
// ESFlags(1)
|
|
// DecConfigDesc(
|
|
// MP4DecConfigDescrTag
|
|
// objectId streamType bufSize avgBitrate
|
|
// DecSpecificDesc(
|
|
// MP4DecSpecificDescrTag
|
|
// decConfig
|
|
// )
|
|
// )
|
|
// ?Desc(lenDescHdr+1)
|
|
// )
|
|
|
|
func (self ElemStreamDesc) Marshal(b []byte) (n int) {
|
|
pio.PutU32BE(b[4:], uint32(ESDS))
|
|
n += 8
|
|
pio.PutU32BE(b[n:], 0) // Version
|
|
n += 4
|
|
datalen := self.Len()
|
|
n += self.fillESDescHdr(b[n:], datalen-n-self.lenESDescHdr())
|
|
n += self.fillDecConfigDescHdr(b[n:], datalen-n-self.lenDescHdr()-1)
|
|
copy(b[n:], self.DecConfig)
|
|
n += len(self.DecConfig)
|
|
n += self.fillDescHdr(b[n:], 0x06, datalen-n-self.lenDescHdr())
|
|
b[n] = 0x02
|
|
n++
|
|
pio.PutU32BE(b[0:], uint32(n))
|
|
return
|
|
}
|
|
|
|
func (self *ElemStreamDesc) Unmarshal(b []byte, offset int) (n int, err error) {
|
|
if len(b) < n+12 {
|
|
err = parseErr("hdr", offset+n, err)
|
|
return
|
|
}
|
|
(&self.AtomPos).setPos(offset, len(b))
|
|
n += 8
|
|
n += 4
|
|
return self.parseDesc(b[n:], offset+n)
|
|
}
|
|
|
|
func (self *ElemStreamDesc) parseDesc(b []byte, offset int) (n int, err error) {
|
|
var hdrlen int
|
|
var datalen int
|
|
var tag uint8
|
|
if hdrlen, tag, datalen, err = self.parseDescHdr(b, offset); err != nil {
|
|
return
|
|
}
|
|
n += hdrlen
|
|
|
|
if len(b) < n+datalen {
|
|
err = parseErr("datalen", offset+n, err)
|
|
return
|
|
}
|
|
|
|
switch tag {
|
|
case MP4ESDescrTag:
|
|
if len(b) < n+3 {
|
|
err = parseErr("MP4ESDescrTag", offset+n, err)
|
|
return
|
|
}
|
|
if _, err = self.parseDesc(b[n+3:], offset+n+3); err != nil {
|
|
return
|
|
}
|
|
|
|
case MP4DecConfigDescrTag:
|
|
const size = 2 + 3 + 4 + 4
|
|
if len(b) < n+size {
|
|
err = parseErr("MP4DecSpecificDescrTag", offset+n, err)
|
|
return
|
|
}
|
|
if _, err = self.parseDesc(b[n+size:], offset+n+size); err != nil {
|
|
return
|
|
}
|
|
|
|
case MP4DecSpecificDescrTag:
|
|
self.DecConfig = b[n:]
|
|
}
|
|
|
|
n += datalen
|
|
return
|
|
}
|
|
|
|
func (self *ElemStreamDesc) parseLength(b []byte, offset int) (n int, length int, err error) {
|
|
for n < 4 {
|
|
if len(b) < n+1 {
|
|
err = parseErr("len", offset+n, err)
|
|
return
|
|
}
|
|
c := b[n]
|
|
n++
|
|
length = (length << 7) | (int(c) & 0x7f)
|
|
if c&0x80 == 0 {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (self *ElemStreamDesc) parseDescHdr(b []byte, offset int) (n int, tag uint8, datalen int, err error) {
|
|
if len(b) < n+1 {
|
|
err = parseErr("tag", offset+n, err)
|
|
return
|
|
}
|
|
tag = b[n]
|
|
n++
|
|
var lenlen int
|
|
if lenlen, datalen, err = self.parseLength(b[n:], offset+n); err != nil {
|
|
return
|
|
}
|
|
n += lenlen
|
|
return
|
|
}
|
|
|
|
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 MOOV:
|
|
atom = &Movie{}
|
|
case MOOF:
|
|
atom = &MovieFrag{}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func (self MovieHeader) String() string {
|
|
return fmt.Sprintf("dur=%d", self.Duration)
|
|
}
|
|
|
|
func (self TimeToSample) String() string {
|
|
return fmt.Sprintf("entries=%d", len(self.Entries))
|
|
}
|
|
|
|
func (self SampleToChunk) String() string {
|
|
return fmt.Sprintf("entries=%d", len(self.Entries))
|
|
}
|
|
|
|
func (self SampleSize) String() string {
|
|
return fmt.Sprintf("entries=%d", len(self.Entries))
|
|
}
|
|
|
|
func (self SyncSample) String() string {
|
|
return fmt.Sprintf("entries=%d", len(self.Entries))
|
|
}
|
|
|
|
func (self CompositionOffset) String() string {
|
|
return fmt.Sprintf("entries=%d", len(self.Entries))
|
|
}
|
|
|
|
func (self ChunkOffset) String() string {
|
|
return fmt.Sprintf("entries=%d", len(self.Entries))
|
|
}
|
|
|
|
func (self TrackFragRun) String() string {
|
|
return fmt.Sprintf("dataoffset=%d", self.DataOffset)
|
|
}
|
|
|
|
func (self TrackFragHeader) String() string {
|
|
return fmt.Sprintf("basedataoffset=%d", self.BaseDataOffset)
|
|
}
|
|
|
|
func (self ElemStreamDesc) String() string {
|
|
return fmt.Sprintf("configlen=%d", len(self.DecConfig))
|
|
}
|
|
|
|
func (self *Track) GetAVC1Conf() (conf *AVC1Conf) {
|
|
atom := FindChildren(self, AVCC)
|
|
conf, _ = atom.(*AVC1Conf)
|
|
return
|
|
}
|
|
|
|
func (self *Track) GetElemStreamDesc() (esds *ElemStreamDesc) {
|
|
atom := FindChildren(self, ESDS)
|
|
esds, _ = atom.(*ElemStreamDesc)
|
|
return
|
|
}
|