Files
go2rtc/pkg/iso/reader.go
2025-04-04 19:58:05 +03:00

204 lines
4.0 KiB
Go

package iso
import (
"bytes"
"encoding/binary"
"io"
"github.com/AlexxIT/go2rtc/pkg/bits"
)
type Atom struct {
Name string
Data []byte
}
type AtomTkhd struct {
TrackID uint32
}
type AtomMdhd struct {
TimeScale uint32
}
type AtomVideo struct {
Name string
Config []byte
}
type AtomAudio struct {
Name string
Channels uint16
SampleRate uint32
Config []byte
}
type AtomMfhd struct {
Sequence uint32
}
type AtomMdat struct {
Data []byte
}
type AtomTfhd struct {
TrackID uint32
SampleDuration uint32
SampleSize uint32
SampleFlags uint32
}
type AtomTfdt struct {
DecodeTime uint64
}
type AtomTrun struct {
DataOffset uint32
FirstSampleFlags uint32
SamplesDuration []uint32
SamplesSize []uint32
SamplesFlags []uint32
SamplesCTS []uint32
}
func DecodeAtom(b []byte) (any, error) {
size := binary.BigEndian.Uint32(b)
if len(b) < int(size) {
return nil, io.EOF
}
name := string(b[4:8])
data := b[8:size]
switch name {
// useful containers
case Moov, MoovTrak, MoovTrakMdia, MoovTrakMdiaMinf, MoovTrakMdiaMinfStbl, Moof, MoofTraf:
return DecodeAtoms(data)
case MoovTrakTkhd:
return &AtomTkhd{TrackID: binary.BigEndian.Uint32(data[1+3+4+4:])}, nil
case MoovTrakMdiaMdhd:
return &AtomMdhd{TimeScale: binary.BigEndian.Uint32(data[1+3+4+4:])}, nil
case MoovTrakMdiaMinfStblStsd:
// support only 1 codec entry
if n := binary.BigEndian.Uint32(data[1+3:]); n == 1 {
return DecodeAtom(data[1+3+4:])
}
case "avc1", "hev1":
b = data[6+2+2+2+4+4+4+2+2+4+4+4+2+32+2+2:]
atom, err := DecodeAtom(b)
if err != nil {
return nil, err
}
if conf, ok := atom.(*Atom); ok {
return &AtomVideo{Name: name, Config: conf.Data}, nil
}
case "mp4a":
atom := &AtomAudio{Name: name}
rd := bits.NewReader(data)
rd.ReadBytes(6 + 2 + 2 + 2 + 4) // skip
atom.Channels = rd.ReadUint16()
rd.ReadBytes(2 + 2 + 2) // skip
atom.SampleRate = uint32(rd.ReadFloat32())
atom2, _ := DecodeAtom(rd.Left())
if conf, ok := atom2.(*Atom); ok {
_, b, _ = bytes.Cut(conf.Data, []byte{5, 0x80, 0x80, 0x80})
if n := len(b); n > 0 && n > 1+int(b[0]) {
atom.Config = b[1 : 1+b[0]]
}
}
return atom, nil
case MoofMfhd:
return &AtomMfhd{Sequence: binary.BigEndian.Uint32(data[4:])}, nil
case MoofTrafTfhd:
rd := bits.NewReader(data)
_ = rd.ReadByte() // version
flags := rd.ReadUint24()
atom := &AtomTfhd{
TrackID: rd.ReadUint32(),
}
if flags&TfhdDefaultSampleDuration != 0 {
atom.SampleDuration = rd.ReadUint32()
}
if flags&TfhdDefaultSampleSize != 0 {
atom.SampleSize = rd.ReadUint32()
}
if flags&TfhdDefaultSampleFlags != 0 {
atom.SampleFlags = rd.ReadUint32() // skip
}
return atom, nil
case MoofTrafTfdt:
return &AtomTfdt{DecodeTime: binary.BigEndian.Uint64(data[4:])}, nil
case MoofTrafTrun:
rd := bits.NewReader(data)
_ = rd.ReadByte() // version
flags := rd.ReadUint24()
samples := rd.ReadUint32()
atom := &AtomTrun{}
if flags&TrunDataOffset != 0 {
atom.DataOffset = rd.ReadUint32()
}
if flags&TrunFirstSampleFlags != 0 {
atom.FirstSampleFlags = rd.ReadUint32()
}
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 {
atom.SamplesFlags = append(atom.SamplesFlags, rd.ReadUint32())
}
if flags&TrunSampleCTS != 0 {
atom.SamplesCTS = append(atom.SamplesCTS, rd.ReadUint32())
}
}
return atom, nil
case Mdat:
return &AtomMdat{Data: data}, nil
}
return &Atom{Name: name, Data: data}, nil
}
func DecodeAtoms(b []byte) (atoms []any, err error) {
for len(b) > 0 {
atom, err := DecodeAtom(b)
if err != nil {
return nil, err
}
if childs, ok := atom.([]any); ok {
atoms = append(atoms, childs...)
} else {
atoms = append(atoms, atom)
}
size := binary.BigEndian.Uint32(b)
b = b[size:]
}
return atoms, nil
}