Add MP4 atoms reader

This commit is contained in:
Alexey Khit
2023-08-14 14:49:16 +03:00
parent de6bb33f01
commit 8fbfccd024
4 changed files with 172 additions and 48 deletions
+33 -10
View File
@@ -15,18 +15,39 @@ func NewReader(b []byte) *Reader {
//goland:noinspection GoStandardMethods
func (r *Reader) ReadByte() byte {
if r.bits == 0 {
if r.pos >= len(r.buf) {
r.EOF = true
return 0
}
b := r.buf[r.pos]
r.pos++
return b
if r.bits != 0 {
return r.ReadBits8(8)
}
return r.ReadBits8(8)
if r.pos >= len(r.buf) {
r.EOF = true
return 0
}
b := r.buf[r.pos]
r.pos++
return b
}
func (r *Reader) ReadUint16() uint16 {
if r.bits != 0 {
return r.ReadBits16(16)
}
return uint16(r.ReadByte())<<8 | uint16(r.ReadByte())
}
func (r *Reader) ReadUint24() uint32 {
if r.bits != 0 {
return r.ReadBits(24)
}
return uint32(r.ReadByte())<<16 | uint32(r.ReadByte())<<8 | uint32(r.ReadByte())
}
func (r *Reader) ReadUint32() uint32 {
if r.bits != 0 {
return r.ReadBits(32)
}
return uint32(r.ReadByte())<<24 | uint32(r.ReadByte())<<16 | uint32(r.ReadByte())<<8 | uint32(r.ReadByte())
}
func (r *Reader) ReadBit() byte {
@@ -61,6 +82,7 @@ func (r *Reader) ReadBits16(n byte) (res uint16) {
return
}
// ReadUEGolomb - ReadExponentialGolomb (unsigned)
func (r *Reader) ReadUEGolomb() uint32 {
var size byte
for size = 0; size < 32; size++ {
@@ -71,6 +93,7 @@ func (r *Reader) ReadUEGolomb() uint32 {
return r.ReadBits(size) + (1 << size) - 1
}
// ReadSEGolomb - ReadSignedExponentialGolomb
func (r *Reader) ReadSEGolomb() int32 {
if b := r.ReadUEGolomb(); b%2 == 0 {
return -int32(b >> 1)
+16 -16
View File
@@ -260,6 +260,22 @@ func (m *Movie) WriteAudioTrack(id uint32, codec string, timescale uint32, chann
m.EndAtom() // TRAK
}
const (
TfhdDefaultSampleDuration = 0x000008
TfhdDefaultSampleSize = 0x000010
TfhdDefaultSampleFlags = 0x000020
TfhdDefaultBaseIsMoof = 0x020000
)
const (
TrunDataOffset = 0x000001
TrunFirstSampleFlags = 0x000004
TrunSampleDuration = 0x0000100
TrunSampleSize = 0x0000200
TrunSampleFlags = 0x0000400
TrunSampleCTS = 0x0000800
)
func (m *Movie) WriteMovieFragment(seq, tid, duration, size, flags uint32, time uint64) {
m.StartAtom(Moof)
@@ -271,13 +287,6 @@ func (m *Movie) WriteMovieFragment(seq, tid, duration, size, flags uint32, time
m.StartAtom(MoofTraf)
const (
TfhdDefaultSampleDuration = 0x000008
TfhdDefaultSampleSize = 0x000010
TfhdDefaultSampleFlags = 0x000020
TfhdDefaultBaseIsMoof = 0x020000
)
m.StartAtom(MoofTrafTfhd)
m.Skip(1) // version
m.WriteUint24(
@@ -298,15 +307,6 @@ func (m *Movie) WriteMovieFragment(seq, tid, duration, size, flags uint32, time
m.WriteUint64(time) // base media decode time
m.EndAtom()
const (
TrunDataOffset = 0x000001
TrunFirstSampleFlags = 0x000004
TrunSampleDuration = 0x0000100
TrunSampleSize = 0x0000200
TrunSampleFlags = 0x0000400
TrunSampleCTS = 0x0000800
)
m.StartAtom(MoofTrafTrun)
m.Skip(1) // version
m.WriteUint24(TrunDataOffset) // flags
+99
View File
@@ -0,0 +1,99 @@
package iso
import (
"encoding/binary"
"io"
"github.com/AlexxIT/go2rtc/pkg/bits"
)
type Atom struct {
Name string
Data []byte
DecodeTime uint64
SamplesDuration []uint32
SamplesSize []uint32
}
func DecodeAtoms(b []byte) ([]*Atom, error) {
var atoms []*Atom
for len(b) > 8 {
size := binary.BigEndian.Uint32(b)
if uint32(len(b)) < size {
return nil, io.EOF
}
name := string(b[4:8])
data := b[8:size]
b = b[size:]
switch name {
case Moof, MoofTraf:
childs, err := DecodeAtoms(data)
if err != nil {
return nil, err
}
atoms = append(atoms, childs...)
case MoofMfhd, MoofTrafTfhd:
continue
case MoofTrafTfdt:
if len(data) < 8 {
return nil, io.EOF
}
dt := binary.BigEndian.Uint64(data[4:])
atoms = append(atoms, &Atom{Name: name, DecodeTime: dt})
case MoofTrafTrun:
rd := bits.NewReader(data)
_ = rd.ReadByte() // version
flags := rd.ReadUint24()
samples := rd.ReadUint32()
if flags&TrunDataOffset != 0 {
_ = rd.ReadUint32() // skip
}
if flags&TrunFirstSampleFlags != 0 {
_ = rd.ReadUint32() // skip
}
atom := &Atom{Name: name}
for i := uint32(0); i < samples; i++ {
if flags&TrunSampleDuration != 0 {
atom.SamplesDuration = append(atom.SamplesDuration, rd.ReadUint32())
}
if flags&TrunSampleSize != 0 {
atom.SamplesSize = append(atom.SamplesSize, rd.ReadUint32())
}
if flags&TrunSampleFlags != 0 {
_ = rd.ReadUint32() // skip
}
if flags&TrunSampleCTS != 0 {
_ = rd.ReadUint32() // skip
}
}
if rd.EOF {
return nil, io.EOF
}
atoms = append(atoms, atom)
case Mdat:
atoms = append(atoms, &Atom{Name: name, Data: data})
default:
println("iso: unsupported atom: " + name)
}
}
return atoms, nil
}
+24 -22
View File
@@ -13,7 +13,7 @@ import (
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/h264/avc"
"github.com/deepch/vdk/format/fmp4/fmp4io"
"github.com/AlexxIT/go2rtc/pkg/iso"
"github.com/gorilla/websocket"
"github.com/pion/rtp"
)
@@ -232,31 +232,37 @@ func (c *Client) getTracks() error {
func (c *Client) worker(buffer chan []byte) {
for data := range buffer {
moof := &fmp4io.MovieFrag{}
if _, err := moof.Unmarshal(data, 0); err != nil {
atoms, err := iso.DecodeAtoms(data)
if err != nil {
continue
}
moofLen := binary.BigEndian.Uint32(data)
_ = moofLen
var trun *iso.Atom
var ts uint32
mdat := moof.Unknowns[0]
if mdat.Tag() != fmp4io.MDAT {
for _, atom := range atoms {
switch atom.Name {
case iso.MoofTrafTrun:
trun = atom
case iso.MoofTrafTfdt:
ts = uint32(atom.DecodeTime)
case iso.Mdat:
data = atom.Data
}
}
if trun == nil || trun.SamplesDuration == nil || trun.SamplesSize == nil {
continue
}
i, _ := mdat.Pos() // offset, size
data = data[i+8:]
traf := moof.Tracks[0]
ts := uint32(traf.DecodeTime.Time)
for i := 0; i < len(trun.SamplesDuration); i++ {
duration := trun.SamplesDuration[i]
size := trun.SamplesSize[i]
//println("!!!", (time.Duration(ts) * time.Millisecond).String(), time.Since(c.t0).String())
for _, entry := range traf.Run.Entries {
// synchronize framerate for WebRTC and MSE
d := time.Duration(ts)*time.Millisecond - time.Since(c.t0)
if d < 0 {
d = time.Duration(entry.Duration) * time.Millisecond / 2
d = time.Duration(duration) * time.Millisecond / 2
}
time.Sleep(d)
@@ -264,16 +270,12 @@ func (c *Client) worker(buffer chan []byte) {
packet := &rtp.Packet{
// ivideon clockrate=1000, RTP clockrate=90000
Header: rtp.Header{Timestamp: ts * 90},
Payload: data[:entry.Size],
Payload: data[:size],
}
c.receiver.WriteRTP(packet)
data = data[entry.Size:]
ts += entry.Duration
}
if len(data) != 0 {
continue
data = data[size:]
ts += duration
}
}
}