in progress

This commit is contained in:
langhuihui
2025-02-08 17:36:28 +08:00
parent 180e766a24
commit b5dd841b20
50 changed files with 4250 additions and 3157 deletions

View File

@@ -4,8 +4,6 @@ global:
loglevel: debug
admin:
enablelogin: false
subscribe:
subaudio: false
# db:
# dbtype: mysql
# dsn: root:Monibuca#!4@tcp(sh-cynosdbmysql-grp-kxt43lv6.sql.tencentcdb.com:28520)/lkm7s_v5?parseTime=true
@@ -32,7 +30,7 @@ mp4:
# ^live/.+:
# fragment: 10s
# filepath: record/$0
# type: fmp4
# type: mp4
onsub:
pull:
^vod_mp4_\d+/(.+)$: $1

View File

@@ -62,12 +62,13 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) {
}
p.DB.Where(&queryRecord).Find(&streams, "end_time>? AND start_time<? AND stream_path=?", startTime, endTime, streamPath)
muxer := mp4.NewMuxer(0)
var n int
n, err = w.Write(box.MakeFtypBox(box.TypeISOM, 0x200, box.TypeISOM, box.TypeISO2, box.TypeAVC1, box.TypeMP41))
ftyp := box.CreateFTYPBox(box.TypeISOM, 0x200, box.TypeISOM, box.TypeISO2, box.TypeAVC1, box.TypeMP41)
var n int64
n, err = box.WriteTo(w, ftyp)
if err != nil {
return
}
muxer.CurrentOffset = int64(n)
muxer.CurrentOffset = n
var lastTs, tsOffset int64
var parts []*ContentPart
sampleOffset := muxer.CurrentOffset + box.BasicBoxLen*2
@@ -148,16 +149,14 @@ func (p *MP4Plugin) download(w http.ResponseWriter, r *http.Request) {
if err != nil {
return
}
var mdatBox = box.MediaDataBox(sampleOffset - mdatOffset)
boxLen, buf := mdatBox.Encode()
if boxLen == box.BasicBoxLen*2 {
w.Write(buf)
} else {
freeBox := box.NewBasicBox(box.TypeFREE)
freeBox.Size = box.BasicBoxLen
_, free := freeBox.Encode()
w.Write(free)
w.Write(buf)
var mdatBox = box.CreateBaseBox(box.TypeMDAT, uint64(sampleOffset-mdatOffset)+box.BasicBoxLen)
var freeBox *box.FreeBox
if mdatBox.HeaderSize() == box.BasicBoxLen {
freeBox = box.CreateFreeBox(nil)
}
_, err = box.WriteTo(w, freeBox, mdatBox)
if err != nil {
return
}
var written, totalWritten int64
for _, part := range parts {

View File

@@ -0,0 +1,85 @@
# MP4 Box Structure
| Col1 | Col2 | Col3 | Col4 | Col5 | Col6 | Required | Description |
|------|------|------|------|------|------|----------|-------------|
| ftyp | | | | | | ✓ | file type and compatibility<br>文件类型和兼容性 |
| pdin | | | | | | | progressive download information |
| moov | | | | | | ✓ | container for all the metadata<br>所有元数据的容器 |
| | mvhd | | | | | ✓ | movie header, overall declarations<br>电影头,整体声明 |
| | trak | | | | | ✓ | container for an individual track or stream<br>单个轨或流的容器 |
| | | tkhd | | | | ✓ | track header, overall information about the track<br>轨的头部,关于该轨的概括信息,比如视频宽高 |
| | | tref | | | | | track reference container |
| | | edts | | | | | edit list container |
| | | | elst | | | | an edit list |
| | | mdia | | | | ✓ | container for the media information in a track<br>轨媒体信息的容器 |
| | | | mdhd | | | ✓ | media header, overall information about the media<br>媒体头,关于媒体的总体信息 |
| | | | hdlr | | | ✓ | handler, declares the media (handler) type<br>媒体的播放过程信息 |
| | | | minf | | | ✓ | media information container<br>媒体信息容器 |
| | | | | vmhd | | | video media header, overall information (video track only) |
| | | | | hmhd | | | hint media header, overall information (hint track only) |
| | | | | nmhd | | | Null media header, overall information (some tracks only) |
| | | | | dinf | | ✓ | data information box, container<br>数据信息box容器 |
| | | | | | dref | ✓ | data reference box, declares source(s) of media data in track<br>如何定位媒体信息 |
| | | | | stbl | | ✓ | sample table box, container for the time/space map<br>包含了track中的sample的所有时间和位置信息以及sample的编解码等信息 |
| | | | | | stsd | ✓ | sample descriptions (codec types, initialization etc.)<br>如果是视频,包含:编码类型、宽高、长度等信息;<br>如果是音频,包含:声道、采样率等信息 |
| | | | | | stts | ✓ | (decoding) time-to-sample<br>描述了sample时序的映射方法我们可以通过它找到任何时间的sample |
| | | | | | ctts | | (composition) time to sample |
| | | | | | stsc | ✓ | sample-to-chunk, partial data-offset information<br>用chunk组织sample可以方便优化数据获取一个chunk包含一个或多个sample |
| | | | | | stsz | | sample sizes (framing)<br>每个sample的大小<br>虽然这里没有打勾但对于mp4还是非常必要的 |
| | | | | | stz2 | | compact sample sizes (framing) |
| | | | | | stco | ✓ | chunk offset, partial data-offset information<br>定义了每个chunk在媒体流中的偏移位置 |
| | | | | | co64 | | 64-bit chunk offset |
| | | | | | stss | | sync sample table (random access points)<br>用于确定media中的关键帧 |
| | | | | | stsh | | shadow sync sample table |
| | | | | | padb | | sample padding bits |
| | | | | | stdp | | sample degradation priority |
| | | | | | sdtp | | independent and disposable samples |
| | | | | | sbgp | | sample-to-group |
| | | | | | sgpd | | sample group description |
| | | | | | subs | | sub-sample information |
| | mvex | | | | | | movie extends box |
| | | mehd | | | | | movie extends header box |
| | | trex | | | | ✓ | track extends defaults |
| | ipmc | | | | | | IPMP Control Box |
| moof | | | | | | | movie fragment |
| | mfhd | | | | | ✓ | movie fragment header |
| | traf | | | | | | track fragment |
| | | | tfhd | | | ✓ | track fragment header |
| | | | trun | | | | track fragment run |
| | | | sdtp | | | | independent and disposable samples |
| | | | sbgp | | | | sample-to-group subs sub-sample information |
| mfra | | | | | | | movie fragment random access |
| | tfra | | | | | | track fragment random access |
| | mfro | | | | | ✓ | movie fragment random access offset |
| mdat | | | | | | | media data container |
| free | | | | | | | free space |
| skip | | | | | | | free space |
| | udta | | | | | | user-data |
| | | cprt | | | | | copyright etc. |
| meta | | | | | | | metadata |
| | hdlr | | | | | ✓ | handler, declares the metadata (handler) type |
| | dinf | | | | | | data information box, container |
| | | dref | | | | | data reference box, declares source(s) of metadata items |
| | ipmc | | | | | | IPMP Control Box |
| | iloc | | | | | | item location |
| | ipro | | | | | | item protection |
| | | sinf | | | | | protection scheme information box |
| | | | frma | | | | original format box |
| | | | imif | | | | IPMP Information box |
| | | | schm | | | | scheme type box |
| | | | schi | | | | scheme information box |
| | iinf | | | | | | item information |
| | xml | | | | | | XML container |
| | bxml | | | | | | binary XML container |
| | pitm | | | | | | primary item reference |
| fiin | | | | | | | file delivery item information |
| | | paen | | | | | partition entry |
| | | | fpar | | | | file partition |
| | | | | fecr | | | FEC reservoir |
| | | | segr | | | | file delivery session group |
| | | | gitn | | | | group id to name |
| | | | tsel | | | | track selection |
| meco | | | | | | | additional metadata container |
| | mere | | | | | | metabox relation |
> Source: [MP4格式分析](https://blog.csdn.net/weixin_41643938/article/details/124542849)

View File

@@ -2,16 +2,253 @@ package box
import (
"encoding/binary"
"fmt"
"io"
"net"
"reflect"
"unsafe"
)
type (
BoxType [4]byte
BoxHeader interface {
Type() BoxType
HeaderSize() uint32
Size() uint32
Header() BoxHeader
HeaderWriteTo(w io.Writer) (n int64, err error)
}
IBox interface {
BoxHeader
io.WriterTo
Unmarshal(buf []byte) (IBox, error)
}
// 基础Box结构实现通用字段
BaseBox struct {
typ BoxType
size uint32
}
DataBox struct {
BaseBox
Data []byte
}
BigBox struct {
BaseBox
size uint64
}
FullBox struct {
BaseBox
Version uint8
Flags [3]byte
}
ContainerBox struct {
BaseBox
Children []IBox
}
)
func CreateBaseBox(typ BoxType, size uint64) IBox {
if size > 0xFFFFFFFF {
return &BigBox{
BaseBox: BaseBox{
typ: typ,
size: 1,
},
size: size + 8,
}
}
return &BaseBox{
typ: typ,
size: uint32(size),
}
}
func CreateDataBox(typ BoxType, data []byte) *DataBox {
return &DataBox{
BaseBox: BaseBox{
typ: typ,
size: uint32(len(data)) + BasicBoxLen,
},
Data: data,
}
}
func CreateContainerBox(typ BoxType, children ...IBox) *ContainerBox {
size := uint32(BasicBoxLen)
realChildren := make([]IBox, 0, len(children))
for _, child := range children {
if reflect.ValueOf(child).IsNil() {
continue
}
size += child.Size()
realChildren = append(realChildren, child)
}
return &ContainerBox{
BaseBox: BaseBox{
typ: typ,
size: size,
},
Children: realChildren,
}
}
func (b *BigBox) HeaderSize() uint32 { return BasicBoxLen + 8 }
func (b *BaseBox) Header() BoxHeader { return b }
func (b *BaseBox) HeaderSize() uint32 { return BasicBoxLen }
func (b *BaseBox) Size() uint32 { return b.size }
func (b *BaseBox) Type() BoxType { return b.typ }
func (b *BaseBox) HeaderWriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
binary.BigEndian.PutUint32(tmp[:], b.size)
buffers := net.Buffers{tmp[:], b.typ[:]}
return buffers.WriteTo(w)
}
func (b *BaseBox) WriteTo(w io.Writer) (n int64, err error) {
return
}
func (b *ContainerBox) WriteTo(w io.Writer) (n int64, err error) {
return WriteTo(w, b.Children...)
}
func (b *DataBox) WriteTo(w io.Writer) (n int64, err error) {
_, err = w.Write(b.Data)
return int64(len(b.Data)), err
}
func (b *BaseBox) Unmarshal(buf []byte) (IBox, error) {
return b, nil
}
func (b *DataBox) Unmarshal(buf []byte) (IBox, error) {
b.Data = buf
return b, nil
}
func (b *FullBox) HeaderWriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
binary.BigEndian.PutUint32(tmp[:], b.size)
buffers := net.Buffers{tmp[:], b.typ[:], []byte{b.Version}, b.Flags[:]}
return buffers.WriteTo(w)
}
func (b *BigBox) HeaderWriteTo(w io.Writer) (n int64, err error) {
n, err = b.BaseBox.HeaderWriteTo(w)
if err != nil {
return
}
var tmp [8]byte
binary.BigEndian.PutUint64(tmp[:], b.size)
_, err = w.Write(tmp[:])
return n + 8, err
}
func (b *BigBox) Header() BoxHeader { return b }
func (b *FullBox) Header() BoxHeader { return b }
func (b *FullBox) HeaderSize() uint32 { return FullBoxLen }
func WriteTo(w io.Writer, box ...IBox) (n int64, err error) {
var n1, n2 int64
for _, b := range box {
if b == nil {
continue
}
n1, err = b.HeaderWriteTo(w)
if err != nil {
return
}
n2, err = b.WriteTo(w)
if err != nil {
return
}
if n1 + n2 != int64(b.Size()) {
panic(fmt.Sprintf("write to %s size error, %d != %d", b.Type(), n1 + n2, b.Size()))
}
n += n1 + n2
}
return
}
func ReadFrom(r io.Reader) (box IBox, err error) {
var tmp [8]byte
if _, err = io.ReadFull(r, tmp[:]); err != nil {
return
}
var baseBox BaseBox
baseBox.size = binary.BigEndian.Uint32(tmp[:4])
baseBox.typ = BoxType(tmp[4:])
t, exists := registry[baseBox.typ.Uint32I()]
if !exists {
return nil, fmt.Errorf("unknown box type: %s", baseBox.typ)
}
b := reflect.New(t).Interface().(IBox)
var payload []byte
if baseBox.size == 1 {
if _, err = io.ReadFull(r, tmp[:]); err != nil {
return
}
payload = make([]byte, binary.BigEndian.Uint64(tmp[:])-BasicBoxLen-8)
} else {
payload = make([]byte, baseBox.size-BasicBoxLen)
}
_, err = io.ReadFull(r, payload)
boxHeader := b.Header()
switch header := boxHeader.(type) {
case *BaseBox:
*header = baseBox
box, err = b.Unmarshal(payload)
case *FullBox:
header.BaseBox = baseBox
header.Version = payload[0]
header.Flags = [3]byte(payload[1:4])
box, err = b.Unmarshal(payload[4:])
}
return
}
func (b BoxType) String() string {
return string(b[:])
}
func (b BoxType) Uint32() uint32 {
return binary.BigEndian.Uint32(b[:])
}
func (b BoxType) Uint32I() uint32 {
return *(*uint32)(unsafe.Pointer(&b[0]))
}
var registry = map[uint32]reflect.Type{}
// RegisterBox 注册box类型
func RegisterBox[T any](typ ...BoxType) {
var b T
bt := reflect.TypeOf(b)
for _, t := range typ {
registry[t.Uint32I()] = bt
}
}
const (
BasicBoxLen = 8 // size(4) + type(4)
FullBoxLen = 12 // BasicBoxLen + version(1) + flags(3)
)
func f(s string) [4]byte {
return [4]byte([]byte(s))
func f(s string) BoxType {
return BoxType([]byte(s))
}
var (
@@ -56,6 +293,7 @@ var (
TypeMP4A = f("mp4a")
TypeULAW = f("ulaw")
TypeALAW = f("alaw")
TypeDOPS = f("dOps")
TypeOPUS = f("opus")
TypeAVCC = f("avcC")
TypeHVCC = f("hvcC")
@@ -99,18 +337,6 @@ var (
TypeHINT = f("hint")
)
type BoxEncoder interface {
Encode(buf []byte) (int, []byte)
}
type BoxDecoder interface {
Decode(buf []byte) (int, error)
}
type BoxSize interface {
Size() uint64
}
// aligned(8) class Box (unsigned int(32) boxtype, optional unsigned int(8)[16] extended_type) {
// unsigned int(32) size;
// unsigned int(32) type = boxtype;
@@ -123,92 +349,12 @@ type BoxSize interface {
// unsigned int(8)[16] usertype = extended_type;
// }
// }
type IBox interface {
Decode(io.Reader, *BasicBox) (int, error)
}
type BasicBox struct {
Offset int64
Size uint64
Type [4]byte
}
func NewBasicBox(boxtype [4]byte) *BasicBox {
return &BasicBox{
Type: boxtype,
}
}
func (box *BasicBox) Decode(r io.Reader) (nn int, err error) {
if _, err = io.ReadFull(r, box.Type[:]); err != nil {
return
}
nn = 4
if box.Size = uint64(binary.BigEndian.Uint32(box.Type[:])); box.Size == 1 {
var largeSize [8]byte
if _, err = io.ReadFull(r, largeSize[:]); err != nil {
return
}
box.Size = binary.BigEndian.Uint64(largeSize[:])
nn += 8
}
if _, err = io.ReadFull(r, box.Type[:]); err != nil {
return
}
nn += 4
return
}
func (box *BasicBox) Encode() (int, []byte) {
buf := make([]byte, box.Size)
binary.BigEndian.PutUint32(buf, uint32(box.Size))
copy(buf[4:], box.Type[:])
return BasicBoxLen, buf
}
// aligned(8) class FullBox(unsigned int(32) boxtype, unsigned int(8) v, bit(24) f) extends Box(boxtype) {
// unsigned int(8) version = v;
// bit(24) flags = f;
// }
type FullBox struct {
Box *BasicBox
Version uint8
Flags [3]byte
}
func NewFullBox(boxtype [4]byte, version uint8) *FullBox {
return &FullBox{
Box: NewBasicBox(boxtype),
Version: version,
}
}
func (box *FullBox) Size() uint64 {
if box.Box.Size > 0 {
return box.Box.Size
} else {
return FullBoxLen
}
}
func (box *FullBox) Decode(r io.Reader) (int, error) {
buf := make([]byte, 4)
if n, err := io.ReadFull(r, buf); err != nil {
return n, err
}
box.Version = buf[0]
copy(box.Flags[:], buf[1:])
return 4, nil
}
func (box *FullBox) Encode() (int, []byte) {
box.Box.Size = box.Size()
offset, buf := box.Box.Encode()
buf[offset] = box.Version
copy(buf[offset+1:], box.Flags[:])
return offset + 4, buf
}
type TimeToSampleEntry struct {
SampleCount uint32
SampleDelta uint32

View File

@@ -5,68 +5,56 @@ import (
"io"
)
// aligned(8) class CompositionOffsetBox extends FullBox(ctts, version = 0, 0) {
// unsigned int(32) entry_count;
// int i;
// if (version==0) {
// for (i=0; i < entry_count; i++) {
// unsigned int(32) sample_count;
// unsigned int(32) sample_offset;
// }
// }
// else if (version == 1) {
// for (i=0; i < entry_count; i++) {
// unsigned int(32) sample_count;
// signed int(32) sample_offset;
// }
// }
// }
type CompositionOffsetBox []CTTSEntry
func (ctts CompositionOffsetBox) Size() uint64 {
return FullBoxLen + 4 + 8*uint64(len(ctts))
type CTTSBox struct {
FullBox
Entries []CTTSEntry
}
func (ctts *CompositionOffsetBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return
func CreateCTTSBox(entries []CTTSEntry) *CTTSBox {
return &CTTSBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeCTTS,
size: uint32(FullBoxLen + 4 + len(entries)*8),
},
},
Entries: entries,
}
entryCountBuf := make([]byte, 4)
if _, err = io.ReadFull(r, entryCountBuf); err != nil {
return
}
offset = 8
l := binary.BigEndian.Uint32(entryCountBuf)
*ctts = make([]CTTSEntry, l)
}
buf := make([]byte, l*8)
if _, err = io.ReadFull(r, buf); err != nil {
return
func (box *CTTSBox) WriteTo(w io.Writer) (n int64, err error) {
buf := make([]byte, 4+len(box.Entries)*8)
// Write entry count
binary.BigEndian.PutUint32(buf[:4], uint32(len(box.Entries)))
// Write entries
for i, entry := range box.Entries {
binary.BigEndian.PutUint32(buf[4+i*8:], entry.SampleCount)
binary.BigEndian.PutUint32(buf[4+i*8+4:], entry.SampleOffset)
}
idx := 0
for i := 0; i < int(l); i++ {
(*ctts)[i].SampleCount = binary.BigEndian.Uint32(buf[idx:])
_, err = w.Write(buf)
return int64(len(buf)), err
}
func (box *CTTSBox) Unmarshal(buf []byte) (IBox, error) {
entryCount := binary.BigEndian.Uint32(buf[:4])
box.Entries = make([]CTTSEntry, entryCount)
if len(buf) < 4+int(entryCount)*8 {
return nil, io.ErrShortBuffer
}
idx := 4
for i := 0; i < int(entryCount); i++ {
box.Entries[i].SampleCount = binary.BigEndian.Uint32(buf[idx:])
idx += 4
(*ctts)[i].SampleOffset = binary.BigEndian.Uint32(buf[idx:])
box.Entries[i].SampleOffset = binary.BigEndian.Uint32(buf[idx:])
idx += 4
}
offset += idx
return
return box, nil
}
func (ctts CompositionOffsetBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeCTTS, 0)
fullbox.Box.Size = ctts.Size()
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], uint32(len(ctts)))
offset += 4
for _, entry := range ctts {
binary.BigEndian.PutUint32(buf[offset:], entry.SampleCount)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], entry.SampleOffset)
offset += 4
}
return offset, buf
func init() {
RegisterBox[CTTSBox](TypeCTTS)
}

View File

@@ -1,35 +1,194 @@
package box
import "encoding/binary"
import (
"bytes"
"encoding/binary"
"io"
)
// aligned(8) class DataEntryUrlBox (bit(24) flags) extends FullBox(url , version = 0, flags) {
var (
TypeURL = BoxType{'u', 'r', 'l', ' '}
TypeURN = BoxType{'u', 'r', 'n', ' '}
)
// aligned(8) class DataEntryUrlBox (bit(24) flags) extends FullBox('url ', version = 0, flags) {
// string location;
// }
// aligned(8) class DataEntryUrnBox (bit(24) flags) extends FullBox(urn , version = 0, flags) {
// aligned(8) class DataEntryUrnBox (bit(24) flags) extends FullBox('urn ', version = 0, flags) {
// string name;
// string location;
// }
// aligned(8) class DataReferenceBox extends FullBox(dref, version = 0, 0) {
// aligned(8) class DataReferenceBox extends FullBox('dref', version = 0, 0) {
// unsigned int(32) entry_count;
// for (i=1; i <= entry_count; i++) {
// DataEntryBox(entry_version, entry_flags) data_entry;
// }
// }
func MakeDefaultDinfBox() []byte {
dinf := BasicBox{Type: TypeDINF, Size: 36}
offset, dinfbox := dinf.Encode()
binary.BigEndian.PutUint32(dinfbox[offset:], 28)
offset += 4
copy(dinfbox[offset:], TypeDREF[:])
offset += 4
offset += 4
binary.BigEndian.PutUint32(dinfbox[offset:], 1)
offset += 4
binary.BigEndian.PutUint32(dinfbox[offset:], 0xc)
offset += 4
copy(dinfbox[offset:], []byte("url "))
offset += 4
binary.BigEndian.PutUint32(dinfbox[offset:], 1)
return dinfbox
type DataInformationBox struct {
BaseBox
Dref *DataReferenceBox
}
type DataReferenceBox struct {
FullBox
Entries []IBox
}
type DataEntryUrlBox struct {
FullBox
Location string
}
type DataEntryUrnBox struct {
FullBox
Name string
Location string
}
func CreateDataInformationBox() *DataInformationBox {
dref := CreateDataReferenceBox()
return &DataInformationBox{
BaseBox: BaseBox{
typ: TypeDINF,
size: uint32(BasicBoxLen + dref.size),
},
Dref: dref,
}
}
func CreateDataReferenceBox() *DataReferenceBox {
url := CreateDataEntryUrlBox("")
return &DataReferenceBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeDREF,
size: uint32(FullBoxLen + 4 + url.size), // 4 for entry_count
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
Entries: []IBox{url},
}
}
func CreateDataEntryUrlBox(location string) *DataEntryUrlBox {
return &DataEntryUrlBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeURL,
size: uint32(FullBoxLen + len(location)),
},
Version: 0,
Flags: [3]byte{0, 0, 1}, // self-contained flag
},
Location: location,
}
}
func CreateDataEntryUrnBox(name, location string) *DataEntryUrnBox {
return &DataEntryUrnBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeURN,
size: uint32(FullBoxLen + len(name) + 1 + len(location)),
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
Name: name,
Location: location,
}
}
func (box *DataInformationBox) WriteTo(w io.Writer) (n int64, err error) {
return WriteTo(w, box.Dref)
}
func (box *DataInformationBox) Unmarshal(buf []byte) (IBox, error) {
r := bytes.NewReader(buf)
b, err := ReadFrom(r)
if err != nil {
return nil, err
}
if dref, ok := b.(*DataReferenceBox); ok {
box.Dref = dref
}
return box, nil
}
func (box *DataReferenceBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
binary.BigEndian.PutUint32(tmp[:], uint32(len(box.Entries)))
nn, err := w.Write(tmp[:])
if err != nil {
return int64(nn), err
}
n = int64(nn)
for _, entry := range box.Entries {
var en int64
en, err = WriteTo(w, entry)
if err != nil {
return
}
n += en
}
return
}
func (box *DataReferenceBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 4 {
return nil, io.ErrShortBuffer
}
entryCount := binary.BigEndian.Uint32(buf)
r := bytes.NewReader(buf[4:])
box.Entries = make([]IBox, 0, entryCount)
for i := uint32(0); i < entryCount; i++ {
entry, err := ReadFrom(r)
if err != nil {
break
}
box.Entries = append(box.Entries, entry)
}
return box, nil
}
func (box *DataEntryUrlBox) WriteTo(w io.Writer) (n int64, err error) {
if len(box.Location) > 0 {
nn, err := w.Write([]byte(box.Location))
return int64(nn), err
}
return 0, nil
}
func (box *DataEntryUrlBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) > 0 {
box.Location = string(buf)
}
return box, nil
}
func (box *DataEntryUrnBox) WriteTo(w io.Writer) (n int64, err error) {
nn, err := w.Write([]byte(box.Name + "\x00" + box.Location))
return int64(nn), err
}
func (box *DataEntryUrnBox) Unmarshal(buf []byte) (IBox, error) {
parts := bytes.SplitN(buf, []byte{0}, 2)
if len(parts) > 0 {
box.Name = string(parts[0])
if len(parts) > 1 {
box.Location = string(parts[1])
}
}
return box, nil
}
func init() {
RegisterBox[*DataInformationBox](TypeDINF)
RegisterBox[*DataReferenceBox](TypeDREF)
RegisterBox[*DataEntryUrlBox](TypeURL)
RegisterBox[*DataEntryUrnBox](TypeURN)
}

View File

@@ -3,6 +3,7 @@ package box
import (
"encoding/binary"
"io"
"net"
"github.com/yapingcat/gomedia/go-codec"
)
@@ -31,7 +32,7 @@ type ChannelMappingTable struct {
}
type OpusSpecificBox struct {
Box *BasicBox
BaseBox
Version uint8
OutputChannelCount uint8
PreSkip uint16
@@ -40,73 +41,15 @@ type OpusSpecificBox struct {
ChanMapTable *ChannelMappingTable
}
func NewdOpsBox() *OpusSpecificBox {
return &OpusSpecificBox{
Box: NewBasicBox([4]byte{'d', 'O', 'p', 's'}),
}
}
func (dops *OpusSpecificBox) Size() uint64 {
return uint64(8 + 10 + 2 + dops.OutputChannelCount)
}
func (dops *OpusSpecificBox) Encode() (int, []byte) {
dops.Box.Size = dops.Size()
offset, buf := dops.Box.Encode()
buf[offset] = dops.Version
offset++
buf[offset] = dops.OutputChannelCount
offset++
binary.LittleEndian.PutUint16(buf[offset:], dops.PreSkip)
offset += 2
binary.BigEndian.PutUint32(buf[offset:], dops.InputSampleRate)
offset += 4
binary.LittleEndian.PutUint16(buf[offset:], uint16(dops.OutputGain))
offset += 2
if dops.ChanMapTable != nil {
buf[offset] = dops.ChanMapTable.StreamCount
offset++
buf[offset] = dops.ChanMapTable.CoupledCount
offset++
copy(buf[offset:], dops.ChanMapTable.ChannelMapping)
offset += len(dops.ChanMapTable.ChannelMapping)
}
return offset, buf
}
func (dops *OpusSpecificBox) Decode(r io.Reader, size uint32) (offset int, err error) {
dopsBuf := make([]byte, size-BasicBoxLen)
ChannelMappingFamily := 0
if size-BasicBoxLen-10 > 0 {
ChannelMappingFamily = int(size - BasicBoxLen - 10)
}
if _, err = io.ReadFull(r, dopsBuf); err != nil {
return
}
dops.Version = dopsBuf[0]
dops.OutputChannelCount = dopsBuf[1]
dops.PreSkip = binary.BigEndian.Uint16(dopsBuf[2:])
dops.InputSampleRate = binary.BigEndian.Uint32(dopsBuf[4:])
dops.OutputGain = int16(binary.BigEndian.Uint16(dopsBuf[8:]))
dops.ChanMapTable = nil
if ChannelMappingFamily > 0 {
dops.ChanMapTable = &ChannelMappingTable{}
dops.ChanMapTable.StreamCount = dopsBuf[10]
dops.ChanMapTable.CoupledCount = dopsBuf[11]
dops.ChanMapTable.ChannelMapping = make([]byte, ChannelMappingFamily-2)
copy(dops.ChanMapTable.ChannelMapping, dopsBuf[12:])
}
return int(size - BasicBoxLen), nil
}
func MakeOpusSpecificBox(extraData []byte) []byte {
func CreateOpusSpecificBox(extraData []byte) *OpusSpecificBox {
ctx := &codec.OpusContext{}
ctx.ParseExtranData(extraData)
dops := NewdOpsBox()
dops := &OpusSpecificBox{
BaseBox: BaseBox{
typ: TypeDOPS,
size: 0,
},
}
dops.Version = 0
dops.OutputChannelCount = uint8(ctx.ChannelCount)
dops.PreSkip = uint16(ctx.Preskip)
@@ -120,6 +63,67 @@ func MakeOpusSpecificBox(extraData []byte) []byte {
}
copy(dops.ChanMapTable.ChannelMapping, ctx.Channel)
}
_, dopsbox := dops.Encode()
return dopsbox
// Calculate final size
dops.size = uint32(BasicBoxLen + 10) // Base size
if dops.ChanMapTable != nil {
dops.size += uint32(2 + len(dops.ChanMapTable.ChannelMapping))
}
return dops
}
func (box *OpusSpecificBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [12]byte // Buffer for fixed-size fields
buffers := make(net.Buffers, 0, 4) // Estimate initial capacity
// Write fixed fields
tmp[0] = box.Version
tmp[1] = box.OutputChannelCount
binary.BigEndian.PutUint16(tmp[2:], box.PreSkip)
binary.BigEndian.PutUint32(tmp[4:], box.InputSampleRate)
binary.BigEndian.PutUint16(tmp[8:], uint16(box.OutputGain))
if box.ChanMapTable != nil {
tmp[10] = box.ChanMapTable.StreamCount
tmp[11] = box.ChanMapTable.CoupledCount
buffers = append(buffers, tmp[:12])
buffers = append(buffers, box.ChanMapTable.ChannelMapping)
} else {
buffers = append(buffers, tmp[:10])
}
return buffers.WriteTo(w)
}
func (box *OpusSpecificBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 10 {
return nil, io.ErrShortBuffer
}
box.Version = buf[0]
box.OutputChannelCount = buf[1]
box.PreSkip = binary.BigEndian.Uint16(buf[2:])
box.InputSampleRate = binary.BigEndian.Uint32(buf[4:])
box.OutputGain = int16(binary.BigEndian.Uint16(buf[8:]))
// Check if we have channel mapping data
if len(buf) > 10 {
if len(buf) < 12 {
return nil, io.ErrShortBuffer
}
box.ChanMapTable = &ChannelMappingTable{
StreamCount: buf[10],
CoupledCount: buf[11],
}
if len(buf) > 12 {
box.ChanMapTable.ChannelMapping = make([]byte, len(buf)-12)
copy(box.ChanMapTable.ChannelMapping, buf[12:])
}
}
return box, nil
}
func init() {
RegisterBox[*OpusSpecificBox](TypeDOPS)
}

View File

@@ -1,105 +0,0 @@
package box
import (
"encoding/binary"
"io"
)
// aligned(8) class EditListBox extends FullBox(elst, version, 0) {
// unsigned int(32) entry_count;
// for (i=1; i <= entry_count; i++) {
// if (version==1) {
// unsigned int(64) segment_duration;
// int(64) media_time;
// } else { // version==0
// unsigned int(32) segment_duration;
// int(32) media_time;
// }
// int(16) media_rate_integer;
// int(16) media_rate_fraction = 0;
// }
// }
type EditListBox struct {
Version byte
Entrys []ELSTEntry
}
func NewEditListBox(version byte) *EditListBox {
return &EditListBox{
Version: version,
}
}
func (elst *EditListBox) Encode(boxSize int) (int, []byte) {
fullbox := NewFullBox(TypeELST, elst.Version)
fullbox.Box.Size = uint64(boxSize)
offset, elstdata := fullbox.Encode()
binary.BigEndian.PutUint32(elstdata[offset:], uint32(len(elst.Entrys)))
offset += 4
for _, entry := range elst.Entrys {
if elst.Version == 1 {
binary.BigEndian.PutUint64(elstdata[offset:], entry.SegmentDuration)
offset += 8
binary.BigEndian.PutUint64(elstdata[offset:], uint64(entry.MediaTime))
offset += 8
} else {
binary.BigEndian.PutUint32(elstdata[offset:], uint32(entry.SegmentDuration))
offset += 4
binary.BigEndian.PutUint32(elstdata[offset:], uint32(entry.MediaTime))
offset += 4
}
binary.BigEndian.PutUint16(elstdata[offset:], uint16(entry.MediaRateInteger))
offset += 2
binary.BigEndian.PutUint16(elstdata[offset:], uint16(entry.MediaRateFraction))
offset += 2
}
return offset, elstdata
}
func (elst *EditListBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return 0, err
}
entryCountBuf := make([]byte, 4)
if _, err = io.ReadFull(r, entryCountBuf); err != nil {
return
}
entryCount := binary.BigEndian.Uint32(entryCountBuf)
offset += 4
var boxsize uint32
if elst.Version == 0 {
boxsize = 12 * entryCount
} else {
boxsize = 20 * entryCount
}
buf := make([]byte, boxsize)
if _, err := io.ReadFull(r, buf); err != nil {
return 0, err
}
if elst.Entrys == nil {
elst.Entrys = make([]ELSTEntry, entryCount)
}
nn := 0
for i := range entryCount {
entry := &elst.Entrys[i]
if elst.Version == 0 {
entry.SegmentDuration = uint64(binary.BigEndian.Uint32(buf[nn:]))
nn += 4
entry.MediaTime = int64(int32(binary.BigEndian.Uint32(buf[nn:])))
nn += 4
} else {
entry.SegmentDuration = uint64(binary.BigEndian.Uint64(buf[nn:]))
nn += 8
entry.MediaTime = int64(binary.BigEndian.Uint64(buf[nn:]))
nn += 8
}
entry.MediaRateInteger = int16(binary.BigEndian.Uint16(buf[nn:]))
nn += 2
entry.MediaRateFraction = int16(binary.BigEndian.Uint16(buf[nn:]))
nn += 2
}
return offset + nn, nil
}

132
plugin/mp4/pkg/box/elst.go Normal file
View File

@@ -0,0 +1,132 @@
package box
import (
"encoding/binary"
"io"
)
// aligned(8) class EditListBox extends FullBox('elst', version, 0) {
// unsigned int(32) entry_count;
// for (i=1; i <= entry_count; i++) {
// if (version==1) {
// unsigned int(64) segment_duration;
// int(64) media_time;
// } else { // version==0
// unsigned int(32) segment_duration;
// int(32) media_time;
// }
// int(16) media_rate_integer;
// int(16) media_rate_fraction = 0;
// }
// }
type EditListBox struct {
FullBox
Entries []ELSTEntry
}
func CreateEditListBox(version byte, entries []ELSTEntry) *EditListBox {
entrySize := 12 // version 0: 4 + 4 + 2 + 2
if version == 1 {
entrySize = 20 // version 1: 8 + 8 + 2 + 2
}
return &EditListBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeELST,
size: uint32(FullBoxLen + 4 + len(entries)*entrySize),
},
Version: version,
Flags: [3]byte{0, 0, 0},
},
Entries: entries,
}
}
func (box *EditListBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
binary.BigEndian.PutUint32(tmp[:4], uint32(len(box.Entries)))
_, err = w.Write(tmp[:4])
if err != nil {
return
}
n = 4
for _, entry := range box.Entries {
if box.Version == 1 {
binary.BigEndian.PutUint64(tmp[:], entry.SegmentDuration)
_, err = w.Write(tmp[:8])
if err != nil {
return
}
n += 8
binary.BigEndian.PutUint64(tmp[:], uint64(entry.MediaTime))
_, err = w.Write(tmp[:8])
if err != nil {
return
}
n += 8
} else {
binary.BigEndian.PutUint32(tmp[:], uint32(entry.SegmentDuration))
_, err = w.Write(tmp[:4])
if err != nil {
return
}
n += 4
binary.BigEndian.PutUint32(tmp[:], uint32(entry.MediaTime))
_, err = w.Write(tmp[:4])
if err != nil {
return
}
n += 4
}
binary.BigEndian.PutUint16(tmp[:], uint16(entry.MediaRateInteger))
_, err = w.Write(tmp[:2])
if err != nil {
return
}
n += 2
binary.BigEndian.PutUint16(tmp[:], uint16(entry.MediaRateFraction))
_, err = w.Write(tmp[:2])
if err != nil {
return
}
n += 2
}
return
}
func (box *EditListBox) Unmarshal(buf []byte) (IBox, error) {
entryCount := binary.BigEndian.Uint32(buf[:4])
box.Entries = make([]ELSTEntry, entryCount)
offset := 4
for i := range box.Entries {
if box.Version == 1 {
if offset+20 > len(buf) {
return nil, io.ErrShortBuffer
}
box.Entries[i].SegmentDuration = binary.BigEndian.Uint64(buf[offset:])
offset += 8
box.Entries[i].MediaTime = int64(binary.BigEndian.Uint64(buf[offset:]))
offset += 8
} else {
if offset+12 > len(buf) {
return nil, io.ErrShortBuffer
}
box.Entries[i].SegmentDuration = uint64(binary.BigEndian.Uint32(buf[offset:]))
offset += 4
box.Entries[i].MediaTime = int64(int32(binary.BigEndian.Uint32(buf[offset:])))
offset += 4
}
box.Entries[i].MediaRateInteger = int16(binary.BigEndian.Uint16(buf[offset:]))
offset += 2
box.Entries[i].MediaRateFraction = int16(binary.BigEndian.Uint16(buf[offset:]))
offset += 2
}
return box, nil
}
func init() {
RegisterBox[*EditListBox](TypeELST)
}

View File

@@ -2,6 +2,7 @@ package box
import (
"encoding/binary"
"io"
"github.com/yapingcat/gomedia/go-codec"
)
@@ -50,6 +51,21 @@ func (base *BaseDescriptor) Encode() []byte {
return bsw.Bits()[:5+int(base.sizeOfInstance)]
}
type ESDSBox struct {
FullBox
Data []byte
}
func (box *ESDSBox) WriteTo(w io.Writer) (n int64, err error) {
_, err = w.Write(box.Data)
return int64(len(box.Data)), err
}
func (box *ESDSBox) Unmarshal(buf []byte) (IBox, error) {
box.Data = buf
return box, nil
}
// ffmpeg mov_write_esds_tag
func makeBaseDescriptor(tag uint8, size uint32) []byte {
base := BaseDescriptor{
@@ -59,6 +75,19 @@ func makeBaseDescriptor(tag uint8, size uint32) []byte {
return base.Encode()
}
func CreateESDSBox(trackid uint16, cid MP4_CODEC_TYPE, vosData []byte) *ESDSBox {
esData := makeESDescriptor(trackid, cid, vosData)
return &ESDSBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeESDS,
size: uint32(FullBoxLen + len(esData)),
},
},
Data: esData,
}
}
func makeESDescriptor(trackid uint16, cid MP4_CODEC_TYPE, vosData []byte) []byte {
dcd := makeDecoderConfigDescriptor(cid, vosData)
sld := makeSLDescriptor()
@@ -71,7 +100,6 @@ func makeESDescriptor(trackid uint16, cid MP4_CODEC_TYPE, vosData []byte) []byte
}
func makeDecoderConfigDescriptor(cid MP4_CODEC_TYPE, vosData []byte) []byte {
decoder_specific_info_len := uint32(0)
if len(vosData) > 0 {
decoder_specific_info_len = uint32(len(vosData)) + 5
@@ -145,3 +173,7 @@ func DecodeESDescriptor(esd []byte) (cid MP4_CODEC_TYPE, vosData []byte) {
}
return
}
func init() {
RegisterBox[*ESDSBox](TypeESDS)
}

View File

@@ -1,25 +1,11 @@
package box
import (
"io"
)
type FreeBox = DataBox
type FreeBox struct {
Data []byte
func CreateFreeBox(data []byte) *FreeBox {
return CreateDataBox(TypeFREE, data)
}
func (free *FreeBox) Decode(r io.Reader, baseBox *BasicBox) (int, error) {
if BasicBoxLen < baseBox.Size {
free.Data = make([]byte, baseBox.Size-BasicBoxLen)
if _, err := io.ReadFull(r, free.Data); err != nil {
return 0, err
}
}
return int(baseBox.Size - BasicBoxLen), nil
}
func (free *FreeBox) Encode() []byte {
offset, buf := (&BasicBox{Type: TypeFREE, Size: 8 + uint64(len(free.Data))}).Encode()
copy(buf[offset:], free.Data)
return buf
func init() {
RegisterBox[*FreeBox](TypeFREE)
}

View File

@@ -3,58 +3,48 @@ package box
import (
"encoding/binary"
"io"
"net"
)
type FileTypeBox struct {
Major_brand [4]byte
Minor_version uint32
Compatible_brands [][4]byte
BaseBox
MajorBrand BoxType
MinorVersion uint32
CompatibleBrands []BoxType
}
func (ftyp *FileTypeBox) Decode(r io.Reader, baseBox *BasicBox) (int, error) {
buf := make([]byte, baseBox.Size-BasicBoxLen)
if n, err := io.ReadFull(r, buf); err != nil {
return n, err
func CreateFTYPBox(major BoxType, minor uint32, compatibleBrands ...BoxType) *FileTypeBox {
return &FileTypeBox{
BaseBox: BaseBox{
typ: TypeFTYP,
size: uint32(BasicBoxLen + len(compatibleBrands)*4 + 8),
},
MajorBrand: major,
MinorVersion: minor,
CompatibleBrands: compatibleBrands,
}
ftyp.Major_brand = [4]byte(buf[0:])
ftyp.Minor_version = binary.BigEndian.Uint32(buf[4:])
n := 8
for ; BasicBoxLen+n < int(baseBox.Size); n += 4 {
ftyp.Compatible_brands = append(ftyp.Compatible_brands, [4]byte(buf[n:]))
}
func (box *FileTypeBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
buffers := make(net.Buffers, 0, len(box.CompatibleBrands)+2)
binary.BigEndian.PutUint32(tmp[:], box.MinorVersion)
buffers = append(buffers, box.MajorBrand[:], tmp[:])
for _, brand := range box.CompatibleBrands {
buffers = append(buffers, brand[:])
}
return n, nil
return buffers.WriteTo(w)
}
func (ftyp *FileTypeBox) Encode(t [4]byte) (int, []byte) {
var baseBox BasicBox
baseBox.Type = t
baseBox.Size = uint64(BasicBoxLen + len(ftyp.Compatible_brands)*4 + 8)
offset, buf := baseBox.Encode()
copy(buf[offset:], ftyp.Major_brand[:])
offset += 4
binary.BigEndian.PutUint32(buf[offset:], ftyp.Minor_version)
offset += 4
for i := 0; offset < int(baseBox.Size); offset += 4 {
copy(buf[offset:], ftyp.Compatible_brands[i][:])
i++
func (box *FileTypeBox) Unmarshal(buf []byte) (IBox, error) {
box.MajorBrand = BoxType(buf[:4])
box.MinorVersion = binary.BigEndian.Uint32(buf[4:8])
for i := 8; i < len(buf); i += 4 {
box.CompatibleBrands = append(box.CompatibleBrands, BoxType(buf[i:i+4]))
}
return offset, buf
return box, nil
}
func MakeFtypBox(major [4]byte, minor uint32, compatibleBrands ...[4]byte) []byte {
var ftyp FileTypeBox
ftyp.Major_brand = major
ftyp.Minor_version = minor
ftyp.Compatible_brands = compatibleBrands
_, boxData := ftyp.Encode(TypeFTYP)
return boxData
}
func MakeStypBox(major [4]byte, minor uint32, compatibleBrands ...[4]byte) []byte {
var styp FileTypeBox
styp.Major_brand = major
styp.Minor_version = minor
styp.Compatible_brands = compatibleBrands
_, boxData := styp.Encode(TypeSTYP)
return boxData
func init() {
RegisterBox[*FileTypeBox](TypeFTYP, TypeSTYP)
}

View File

@@ -3,6 +3,7 @@ package box
import (
"encoding/binary"
"io"
"net"
)
// Box Type: 'hdlr'
@@ -27,44 +28,43 @@ import (
type HandlerType = [4]byte
type HandlerBox struct {
FullBox
Pre_defined uint32
Handler_type HandlerType
Reserved [3]uint32
Name string
}
func NewHandlerBox(handlerType HandlerType, name string) *HandlerBox {
return &HandlerBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeHDLR,
size: uint32(20 + len(name) + 1 + FullBoxLen),
},
},
Handler_type: handlerType,
Name: name,
}
}
func (hdlr *HandlerBox) Decode(r io.Reader, size uint64) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return 0, err
}
buf := make([]byte, size-FullBoxLen)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
func (hdlr *HandlerBox) Unmarshal(buf []byte) (IBox, error) {
hdlr.Pre_defined = binary.BigEndian.Uint32(buf[:4])
copy(hdlr.Handler_type[:], buf[4:8])
hdlr.Name = string(buf[20 : size-FullBoxLen])
offset = int(size - FullBoxLen)
return
hdlr.Name = string(buf[20:])
return hdlr, nil
}
func (hdlr *HandlerBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeHDLR, 0)
fullbox.Box.Size = 20 + uint64(len(hdlr.Name)+1) + FullBoxLen
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], hdlr.Pre_defined)
copy(buf[offset+4:], hdlr.Handler_type[:])
offset += 20
copy(buf[offset:], []byte(hdlr.Name))
return offset + len(hdlr.Name), buf
func (hdlr *HandlerBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [20]byte
binary.BigEndian.PutUint32(tmp[:], hdlr.Pre_defined)
copy(tmp[4:8], hdlr.Handler_type[:])
var buffer = net.Buffers{
tmp[:],
[]byte(hdlr.Name),
[]byte{0},
}
return buffer.WriteTo(w)
}
func GetHandlerType(cid MP4_CODEC_TYPE) HandlerType {
@@ -79,15 +79,15 @@ func GetHandlerType(cid MP4_CODEC_TYPE) HandlerType {
}
}
func MakeHdlrBox(hdt HandlerType) []byte {
func MakeHdlrBox(hdt HandlerType) *HandlerBox {
var hdlr *HandlerBox = nil
if hdt == TypeVIDE {
hdlr = NewHandlerBox(hdt, "VideoHandler")
} else if hdt == TypeSOUN {
hdlr = NewHandlerBox(hdt, "SoundHandler")
} else {
hdlr = NewHandlerBox(hdt, "")
}
_, boxdata := hdlr.Encode()
return boxdata
return hdlr
}

View File

@@ -6,7 +6,7 @@ import (
)
// aligned(8) class HintMediaHeaderBox
// extends FullBox(hmhd, version = 0, 0) {
// extends FullBox('hmhd', version = 0, 0) {
// unsigned int(16) maxPDUsize;
// unsigned int(16) avgPDUsize;
// unsigned int(32) maxbitrate;
@@ -15,54 +15,50 @@ import (
// }
type HintMediaHeaderBox struct {
FullBox
MaxPDUsize uint16
AvgPDUsize uint16
Maxbitrate uint32
Avgbitrate uint32
}
func NewHintMediaHeaderBox() *HintMediaHeaderBox {
return &HintMediaHeaderBox{}
func CreateHintMediaHeaderBox() *HintMediaHeaderBox {
return &HintMediaHeaderBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeHMHD,
size: uint32(FullBoxLen + 16),
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
}
}
func (hmhd *HintMediaHeaderBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return 0, err
}
buf := make([]byte, 16)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
offset = 0
hmhd.MaxPDUsize = binary.BigEndian.Uint16(buf[offset:])
offset += 2
hmhd.AvgPDUsize = binary.BigEndian.Uint16(buf[offset:])
offset += 2
hmhd.Maxbitrate = binary.BigEndian.Uint32(buf[offset:])
offset += 4
hmhd.Avgbitrate = binary.BigEndian.Uint32(buf[offset:])
offset += 8
func (box *HintMediaHeaderBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [16]byte
binary.BigEndian.PutUint16(tmp[0:], box.MaxPDUsize)
binary.BigEndian.PutUint16(tmp[2:], box.AvgPDUsize)
binary.BigEndian.PutUint32(tmp[4:], box.Maxbitrate)
binary.BigEndian.PutUint32(tmp[8:], box.Avgbitrate)
// reserved already zeroed
nn, err := w.Write(tmp[:])
n = int64(nn)
return
}
func (hmhd *HintMediaHeaderBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeHMHD, 0)
fullbox.Box.Size = FullBoxLen + 16
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint16(buf[offset:], hmhd.MaxPDUsize)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], hmhd.AvgPDUsize)
offset += 2
binary.BigEndian.PutUint32(buf[offset:], hmhd.Maxbitrate)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], hmhd.Avgbitrate)
offset += 8
return offset, buf
func (box *HintMediaHeaderBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 16 {
return nil, io.ErrShortBuffer
}
box.MaxPDUsize = binary.BigEndian.Uint16(buf[0:])
box.AvgPDUsize = binary.BigEndian.Uint16(buf[2:])
box.Maxbitrate = binary.BigEndian.Uint32(buf[4:])
box.Avgbitrate = binary.BigEndian.Uint32(buf[8:])
return box, nil
}
func makeHmhdBox() []byte {
hmhd := NewHintMediaHeaderBox()
_, hmhdbox := hmhd.Encode()
return hmhdbox
func init() {
RegisterBox[*HintMediaHeaderBox](TypeHMHD)
}

View File

@@ -2,25 +2,42 @@ package box
import (
"encoding/binary"
"io"
"net"
)
type MediaDataBox uint64
// aligned(8) class MediaDataBox extends Box('mdat') {
// bit(8) data[];
// }
func (box MediaDataBox) Encode() (int, []byte) {
if box+BasicBoxLen > 0xFFFFFFFF {
basicBox := NewBasicBox(TypeMDAT)
basicBox.Size = BasicBoxLen + 8 + uint64(box)
buf := make([]byte, BasicBoxLen*2)
binary.BigEndian.PutUint32(buf, uint32(1))
copy(buf[4:], basicBox.Type[:])
binary.BigEndian.PutUint64(buf[8:], uint64(box))
return BasicBoxLen * 2, buf
} else {
basicBox := NewBasicBox(TypeMDAT)
basicBox.Size = BasicBoxLen + uint64(box)
buf := make([]byte, BasicBoxLen)
binary.BigEndian.PutUint32(buf, uint32(basicBox.Size))
copy(buf[4:], basicBox.Type[:])
return BasicBoxLen, buf
}
type MediaDataBox struct {
BaseBox
Data []byte
net.Buffers
}
func (box *MediaDataBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
var buffers net.Buffers
if box.size == 1 {
// 写入扩展大小头部
binary.BigEndian.PutUint64(tmp[:], uint64(len(box.Data))+BasicBoxLen+8) // largesize
buffers = append(buffers, tmp[:])
}
if box.Data != nil {
buffers = append(buffers, box.Data)
}
if box.Buffers != nil {
buffers = append(buffers, box.Buffers...)
}
return buffers.WriteTo(w)
}
func (box *MediaDataBox) Unmarshal(buf []byte) (IBox, error) {
box.Data = buf
return box, nil
}
func init() {
RegisterBox[*MediaDataBox](TypeMDAT)
}

View File

@@ -9,7 +9,7 @@ import (
"m7s.live/v5/pkg/util"
)
// aligned(8) class MediaHeaderBox extends FullBox(mdhd, version, 0) {
// aligned(8) class MediaHeaderBox extends FullBox('mdhd', version, 0) {
// if (version==1) {
// unsigned int(64) creation_time;
// unsigned int(64) modification_time;
@@ -40,97 +40,92 @@ func ff_mov_iso639_to_lang(lang [3]byte) (code int) {
}
type MediaHeaderBox struct {
Creation_time uint64
Modification_time uint64
Timescale uint32
Duration uint64
Pad uint8
Language [3]uint8
Pre_defined uint16
FullBox
CreationTime uint64
ModificationTime uint64
Timescale uint32
Duration uint64
Language [3]byte
}
func NewMediaHeaderBox() *MediaHeaderBox {
func CreateMediaHeaderBox(timescale uint32, duration uint64) *MediaHeaderBox {
_, offset := time.Now().Zone()
now := uint64(time.Now().Unix() + int64(offset) + 0x7C25B080)
version := util.Conditional[uint8](duration > 0xFFFFFFFF, 1, 0)
return &MediaHeaderBox{
Creation_time: uint64(time.Now().Unix() + int64(offset) + 0x7C25B080),
Modification_time: uint64(time.Now().Unix() + int64(offset) + 0x7C25B080),
Timescale: 1000,
Language: [3]byte{'u', 'n', 'd'},
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeMDHD,
size: util.Conditional[uint32](version == 1, 32, 20) + FullBoxLen,
},
Version: version,
Flags: [3]byte{0, 0, 0},
},
CreationTime: now,
ModificationTime: now,
Timescale: timescale,
Duration: duration,
Language: [3]byte{'u', 'n', 'd'},
}
}
func (mdhd *MediaHeaderBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return 0, err
func (box *MediaHeaderBox) WriteTo(w io.Writer) (n int64, err error) {
var data []byte
if box.Version == 1 {
data = make([]byte, 32)
binary.BigEndian.PutUint64(data[0:], box.CreationTime)
binary.BigEndian.PutUint64(data[8:], box.ModificationTime)
binary.BigEndian.PutUint32(data[16:], box.Timescale)
binary.BigEndian.PutUint64(data[20:], box.Duration)
binary.BigEndian.PutUint16(data[28:], uint16(ff_mov_iso639_to_lang(box.Language)&0x7FFF))
// pre_defined already zeroed
} else {
data = make([]byte, 20)
binary.BigEndian.PutUint32(data[0:], uint32(box.CreationTime))
binary.BigEndian.PutUint32(data[4:], uint32(box.ModificationTime))
binary.BigEndian.PutUint32(data[8:], box.Timescale)
binary.BigEndian.PutUint32(data[12:], uint32(box.Duration))
binary.BigEndian.PutUint16(data[16:], uint16(ff_mov_iso639_to_lang(box.Language)&0x7FFF))
// pre_defined already zeroed
}
buf := make([]byte, util.Conditional(fullbox.Version == 1, 32, 20))
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
offset = 0
if fullbox.Version == 1 {
mdhd.Creation_time = binary.BigEndian.Uint64(buf[offset:])
offset += 8
mdhd.Modification_time = binary.BigEndian.Uint64(buf[offset:])
offset += 8
mdhd.Timescale = binary.BigEndian.Uint32(buf[offset:])
offset += 4
mdhd.Duration = binary.BigEndian.Uint64(buf[offset:])
offset += 8
} else {
mdhd.Creation_time = uint64(binary.BigEndian.Uint32(buf[offset:]))
offset += 4
mdhd.Modification_time = uint64(binary.BigEndian.Uint32(buf[offset:]))
offset += 4
mdhd.Timescale = binary.BigEndian.Uint32(buf[offset:])
offset += 4
mdhd.Duration = uint64(binary.BigEndian.Uint32(buf[offset:]))
offset += 4
}
bs := codec.NewBitStream(buf[offset:])
mdhd.Pad = bs.GetBit()
mdhd.Language[0] = bs.Uint8(5)
mdhd.Language[1] = bs.Uint8(5)
mdhd.Language[2] = bs.Uint8(5)
mdhd.Pre_defined = 0
offset += 4
nn, err := w.Write(data)
n = int64(nn)
return
}
func (mdhd *MediaHeaderBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeMDHD, 0)
fullbox.Box.Size = util.Conditional[uint64](fullbox.Version == 1, 32, 20) + FullBoxLen
offset, buf := fullbox.Encode()
if fullbox.Version == 1 {
binary.BigEndian.PutUint64(buf[offset:], mdhd.Creation_time)
offset += 8
binary.BigEndian.PutUint64(buf[offset:], mdhd.Modification_time)
offset += 8
binary.BigEndian.PutUint32(buf[offset:], mdhd.Timescale)
offset += 4
binary.BigEndian.PutUint64(buf[offset:], mdhd.Duration)
offset += 8
func (box *MediaHeaderBox) Unmarshal(buf []byte) (IBox, error) {
if box.Version == 1 {
if len(buf) < 32 {
return nil, io.ErrShortBuffer
}
box.CreationTime = binary.BigEndian.Uint64(buf[0:])
box.ModificationTime = binary.BigEndian.Uint64(buf[8:])
box.Timescale = binary.BigEndian.Uint32(buf[16:])
box.Duration = binary.BigEndian.Uint64(buf[20:])
buf = buf[28:]
} else {
binary.BigEndian.PutUint32(buf[offset:], uint32(mdhd.Creation_time))
offset += 4
binary.BigEndian.PutUint32(buf[offset:], uint32(mdhd.Modification_time))
offset += 4
binary.BigEndian.PutUint32(buf[offset:], mdhd.Timescale)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], uint32(mdhd.Duration))
offset += 4
if len(buf) < 20 {
return nil, io.ErrShortBuffer
}
box.CreationTime = uint64(binary.BigEndian.Uint32(buf[0:]))
box.ModificationTime = uint64(binary.BigEndian.Uint32(buf[4:]))
box.Timescale = binary.BigEndian.Uint32(buf[8:])
box.Duration = uint64(binary.BigEndian.Uint32(buf[12:]))
buf = buf[16:]
}
binary.BigEndian.PutUint16(buf[offset:], uint16(ff_mov_iso639_to_lang(mdhd.Language)&0x7FFF))
offset += 2
offset += 2
return offset, buf
// Read language
bs := codec.NewBitStream(buf)
_ = bs.GetBit() // pad
box.Language[0] = bs.Uint8(5)
box.Language[1] = bs.Uint8(5)
box.Language[2] = bs.Uint8(5)
return box, nil
}
func MakeMdhdBox(duration uint32) []byte {
mdhd := NewMediaHeaderBox()
mdhd.Duration = uint64(duration)
_, boxdata := mdhd.Encode()
return boxdata
func init() {
RegisterBox[*MediaHeaderBox](TypeMDHD)
}

View File

@@ -1 +1,72 @@
package box
import (
"bytes"
"io"
)
type MdiaBox struct {
BaseBox
MDHD *MediaHeaderBox
MINF *MediaInformationBox
HDLR *HandlerBox
}
func (m *MdiaBox) WriteTo(w io.Writer) (n int64, err error) {
return WriteTo(w, m.MDHD, m.MINF, m.HDLR)
}
func (m *MdiaBox) Unmarshal(buf []byte) (IBox, error) {
for {
b, err := ReadFrom(bytes.NewReader(buf))
if err != nil {
return nil, err
}
switch box := b.(type) {
case *MediaHeaderBox:
m.MDHD = box
case *MediaInformationBox:
m.MINF = box
case *HandlerBox:
m.HDLR = box
}
}
}
type MediaInformationBox struct {
BaseBox
VMHD *VideoMediaHeaderBox
SMHD *SoundMediaHeaderBox
HMHD *HintMediaHeaderBox
STBL *SampleTableBox
DINF *DataInformationBox
}
func (m *MediaInformationBox) WriteTo(w io.Writer) (n int64, err error) {
return WriteTo(w, m.VMHD, m.SMHD, m.HMHD, m.STBL, m.DINF)
}
func (m *MediaInformationBox) Unmarshal(buf []byte) (IBox, error) {
for {
b, err := ReadFrom(bytes.NewReader(buf))
if err != nil {
return nil, err
}
switch box := b.(type) {
case *VideoMediaHeaderBox:
m.VMHD = box
case *SoundMediaHeaderBox:
m.SMHD = box
case *HintMediaHeaderBox:
m.HMHD = box
case *SampleTableBox:
m.STBL = box
case *DataInformationBox:
m.DINF = box
}
}
}
func init() {
RegisterBox[*MdiaBox](TypeMDIA)
}

View File

@@ -5,39 +5,45 @@ import (
"io"
)
// aligned(8) class MovieFragmentHeaderBox extends FullBox(mfhd, 0, 0){
// aligned(8) class MovieFragmentHeaderBox extends FullBox('mfhd', 0, 0){
// unsigned int(32) sequence_number;
// }
type MovieFragmentHeaderBox uint32
func (mfhd MovieFragmentHeaderBox) Size() uint64 {
return FullBoxLen + 4
type MovieFragmentHeaderBox struct {
FullBox
SequenceNumber uint32
}
func (mfhd *MovieFragmentHeaderBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return
func CreateMovieFragmentHeaderBox(sequenceNumber uint32) *MovieFragmentHeaderBox {
return &MovieFragmentHeaderBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeMFHD,
size: uint32(FullBoxLen + 4),
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
SequenceNumber: sequenceNumber,
}
buf := make([]byte, 4)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
func (box *MovieFragmentHeaderBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
binary.BigEndian.PutUint32(tmp[:], box.SequenceNumber)
nn, err := w.Write(tmp[:])
n = int64(nn)
return
}
func (box *MovieFragmentHeaderBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 4 {
return nil, io.ErrShortBuffer
}
*mfhd = MovieFragmentHeaderBox(binary.BigEndian.Uint32(buf))
return offset + 4, nil
box.SequenceNumber = binary.BigEndian.Uint32(buf[:4])
return box, nil
}
func (mfhd MovieFragmentHeaderBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeMFHD, 0)
fullbox.Box.Size = mfhd.Size()
offset, boxdata := fullbox.Encode()
binary.BigEndian.PutUint32(boxdata[offset:], uint32(mfhd))
return offset + 4, boxdata
}
func MakeMfhdBox(frament uint32) []byte {
mfhd := MovieFragmentHeaderBox(frament)
_, boxData := mfhd.Encode()
return boxData
func init() {
RegisterBox[*MovieFragmentHeaderBox](TypeMFHD)
}

View File

@@ -5,35 +5,43 @@ import (
"io"
)
// aligned(8) class MovieFragmentRandomAccessOffsetBox extends FullBox(mfro, version, 0) {
// aligned(8) class MovieFragmentRandomAccessOffsetBox extends FullBox('mfro', version, 0) {
// unsigned int(32) size;
// }
type MovieFragmentRandomAccessOffsetBox uint32
type MovieFragmentRandomAccessOffsetBox struct {
FullBox
MfraSize uint32
}
func (mfro *MovieFragmentRandomAccessOffsetBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return
func CreateMfroBox(mfraSize uint32) *MovieFragmentRandomAccessOffsetBox {
return &MovieFragmentRandomAccessOffsetBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeMFRO,
size: uint32(FullBoxLen + 4),
},
},
MfraSize: mfraSize,
}
buf := make([]byte, 4)
if _, err = io.ReadFull(r, buf); err != nil {
}
func (box *MovieFragmentRandomAccessOffsetBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
binary.BigEndian.PutUint32(tmp[:], box.MfraSize)
var nn int
nn, err = w.Write(tmp[:])
if err != nil {
return 0, err
}
*mfro = MovieFragmentRandomAccessOffsetBox(binary.BigEndian.Uint32(buf))
return offset + 4, nil
return int64(nn), nil
}
func (mfro *MovieFragmentRandomAccessOffsetBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeMFRO, 0)
fullbox.Box.Size = FullBoxLen + 4
offset, boxdata := fullbox.Encode()
binary.BigEndian.PutUint32(boxdata[offset:], uint32(*mfro))
return offset + 4, boxdata
func (box *MovieFragmentRandomAccessOffsetBox) Unmarshal(buf []byte) (IBox, error) {
box.MfraSize = binary.BigEndian.Uint32(buf)
return box, nil
}
func MakeMfroBox(mfraSize uint32) []byte {
mfro := MovieFragmentRandomAccessOffsetBox(mfraSize + 16)
_, boxData := mfro.Encode()
return boxData
func init() {
RegisterBox[*MovieFragmentRandomAccessOffsetBox](TypeMFRO)
}

View File

@@ -0,0 +1,85 @@
package box
import (
"bytes"
"io"
)
// aligned(8) class MovieFragmentBox extends Box('moof'){
// }
type MovieFragmentBox struct {
BaseBox
MFHD *MovieFragmentHeaderBox
TRAFs []*TrackFragmentBox
}
type TrackFragmentBox struct {
BaseBox
TFHD *TrackFragmentHeaderBox
TFDT *TrackFragmentBaseMediaDecodeTimeBox
TRUN *TrackRunBox
}
func CreateTrackFragmentBox(tfhd *TrackFragmentHeaderBox, tfdt *TrackFragmentBaseMediaDecodeTimeBox, trun *TrackRunBox) *TrackFragmentBox {
return &TrackFragmentBox{
BaseBox: BaseBox{
typ: TypeTRAF,
size: uint32(BasicBoxLen + tfhd.size + trun.size),
},
TFHD: tfhd,
TFDT: tfdt,
TRUN: trun,
}
}
func (box *MovieFragmentBox) WriteTo(w io.Writer) (n int64, err error) {
boxes := []IBox{box.MFHD}
for _, traf := range box.TRAFs {
boxes = append(boxes, traf)
}
return WriteTo(w, boxes...)
}
func (box *TrackFragmentBox) WriteTo(w io.Writer) (n int64, err error) {
return WriteTo(w, box.TFHD, box.TRUN)
}
func (box *MovieFragmentBox) Unmarshal(buf []byte) (IBox, error) {
r := bytes.NewReader(buf)
for {
b, err := ReadFrom(r)
if err != nil {
break
}
switch b := b.(type) {
case *MovieFragmentHeaderBox:
box.MFHD = b
case *TrackFragmentBox:
box.TRAFs = append(box.TRAFs, b)
}
}
return box, nil
}
func (box *TrackFragmentBox) Unmarshal(buf []byte) (IBox, error) {
r := bytes.NewReader(buf)
for {
b, err := ReadFrom(r)
if err != nil {
break
}
switch b := b.(type) {
case *TrackFragmentHeaderBox:
box.TFHD = b
case *TrackRunBox:
box.TRUN = b
}
}
return box, nil
}
func init() {
RegisterBox[*MovieFragmentBox](TypeMOOF)
RegisterBox[*TrackFragmentBox](TypeTRAF)
}

View File

@@ -0,0 +1,70 @@
package box
import (
"bytes"
"io"
)
type (
MoovBox struct {
BaseBox
Tracks []*TrakBox
// UDTA *UdtaBody
MVHD *MovieHeaderBox
MVEX *MovieExtendsBox
}
EdtsBox struct {
BaseBox
Elst *EditListBox
}
)
func (m *MoovBox) WriteTo(w io.Writer) (n int64, err error) {
var boxes []IBox
boxes = append(boxes, m.MVHD)
for _, track := range m.Tracks {
boxes = append(boxes, track)
}
return WriteTo(w, boxes...)
}
func (m *MoovBox) Unmarshal(buf []byte) (IBox, error) {
for {
b, err := ReadFrom(bytes.NewReader(buf))
if err != nil {
return nil, err
}
switch box := b.(type) {
case *TrakBox:
m.Tracks = append(m.Tracks, box)
case *MovieHeaderBox:
m.MVHD = box
case *MovieExtendsBox:
m.MVEX = box
}
}
}
func (e *EdtsBox) WriteTo(w io.Writer) (n int64, err error) {
return WriteTo(w, e.Elst)
}
func (e *EdtsBox) Unmarshal(buf []byte) (IBox, error) {
for {
b, err := ReadFrom(bytes.NewReader(buf))
if err != nil {
return nil, err
}
switch box := b.(type) {
case *EditListBox:
e.Elst = box
}
}
}
func init() {
RegisterBox[*MoovBox](TypeMOOV)
RegisterBox[*EdtsBox](TypeEDTS)
}

View File

@@ -35,13 +35,6 @@ type ELSTEntry struct {
MediaRateFraction int16
}
type TrunEntry struct {
SampleDuration uint32
SampleSize uint32
SampleFlags uint32
SampleCompositionTimeOffset int32
}
type SENC struct {
Entrys []SencEntry
}
@@ -50,3 +43,11 @@ type FragEntry struct {
Time uint64
MoofOffset uint64
}
type TFRAEntry struct {
Time uint64
MoofOffset uint64
TrafNumber uint32
TrunNumber uint32
SampleNumber uint32
}

View File

@@ -0,0 +1,56 @@
package box
import (
"bytes"
"io"
)
// aligned(8) class MovieExtendsBox extends Box('mvex') {
// }
type MovieExtendsBox struct {
BaseBox
Trexs []*TrackExtendsBox
}
func CreateMovieExtendsBox(trexs []*TrackExtendsBox) *MovieExtendsBox {
size := uint32(BasicBoxLen)
for _, trex := range trexs {
size += trex.size
}
return &MovieExtendsBox{
BaseBox: BaseBox{
typ: TypeMVEX,
size: size,
},
Trexs: trexs,
}
}
func (box *MovieExtendsBox) WriteTo(w io.Writer) (n int64, err error) {
boxes := make([]IBox, len(box.Trexs))
for i, trex := range box.Trexs {
boxes[i] = trex
}
return WriteTo(w, boxes...)
}
func (box *MovieExtendsBox) Unmarshal(buf []byte) (IBox, error) {
box.Trexs = make([]*TrackExtendsBox, 0)
r := bytes.NewReader(buf)
for {
b, err := ReadFrom(r)
if err != nil {
break
}
if trex, ok := b.(*TrackExtendsBox); ok {
box.Trexs = append(box.Trexs, trex)
}
}
return box, nil
}
func init() {
RegisterBox[*MovieExtendsBox](TypeMVEX)
}

View File

@@ -6,7 +6,7 @@ import (
"time"
)
// aligned(8) class MovieHeaderBox extends FullBox(mvhd, version, 0) {
// aligned(8) class MovieHeaderBox extends FullBox('mvhd', version, 0) {
// if (version==1) {
// unsigned int(64) creation_time;
// unsigned int(64) modification_time;
@@ -29,121 +29,108 @@ import (
// }
type MovieHeaderBox struct {
Creation_time uint64
Modification_time uint64
Timescale uint32
Duration uint64
Rate uint32
Volume uint16
Matrix [9]uint32
Pre_defined [6]uint32
Next_track_ID uint32
FullBox
CreationTime uint64
ModificationTime uint64
Timescale uint32
Duration uint64
Rate int32
Volume int16
Matrix [9]int32
NextTrackID uint32
}
func NewMovieHeaderBox() *MovieHeaderBox {
// MP4/QuickTime epoch starts from Jan 1, 1904
// Add offset between Unix epoch (1970) and QuickTime epoch (1904)
const mp4Epoch = 2082844800 // seconds between 1904 and 1970
now := uint64(time.Now().Unix() + mp4Epoch)
func CreateMovieHeaderBox(nextTrackID uint32, duration uint32) *MovieHeaderBox {
now := time.Now().Unix()
return &MovieHeaderBox{
Creation_time: now,
Modification_time: now,
Timescale: 1000,
Rate: 0x00010000,
Volume: 0x0100,
Matrix: [9]uint32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeMVHD,
size: uint32(FullBoxLen + 96),
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
CreationTime: uint64(now),
ModificationTime: uint64(now),
Timescale: 1000,
Duration: uint64(duration),
Rate: 0x00010000,
Volume: 0x0100,
Matrix: [9]int32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
NextTrackID: nextTrackID,
}
}
func (mvhd *MovieHeaderBox) Decode(r io.Reader, basebox *BasicBox) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return 0, err
}
boxsize := 0
if fullbox.Version == 0 {
boxsize = 96
func (box *MovieHeaderBox) WriteTo(w io.Writer) (n int64, err error) {
var nn int
if box.Version == 1 {
var tmp [108]byte
binary.BigEndian.PutUint64(tmp[0:], box.CreationTime)
binary.BigEndian.PutUint64(tmp[8:], box.ModificationTime)
binary.BigEndian.PutUint32(tmp[16:], box.Timescale)
binary.BigEndian.PutUint64(tmp[20:], box.Duration)
binary.BigEndian.PutUint32(tmp[28:], uint32(box.Rate))
binary.BigEndian.PutUint16(tmp[32:], uint16(box.Volume))
offset := 34 + 8
for i := 0; i < 9; i++ {
binary.BigEndian.PutUint32(tmp[offset:], uint32(box.Matrix[i]))
offset += 4
}
binary.BigEndian.PutUint32(tmp[104:], box.NextTrackID)
nn, err = w.Write(tmp[:])
n = int64(nn)
} else {
boxsize = 108
var tmp [96]byte
binary.BigEndian.PutUint32(tmp[0:], uint32(box.CreationTime))
binary.BigEndian.PutUint32(tmp[4:], uint32(box.ModificationTime))
binary.BigEndian.PutUint32(tmp[8:], box.Timescale)
binary.BigEndian.PutUint32(tmp[12:], uint32(box.Duration))
binary.BigEndian.PutUint32(tmp[16:], uint32(box.Rate))
binary.BigEndian.PutUint16(tmp[20:], uint16(box.Volume))
offset := 22 + 8
for i := 0; i < 9; i++ {
binary.BigEndian.PutUint32(tmp[offset:], uint32(box.Matrix[i]))
offset += 4
}
binary.BigEndian.PutUint32(tmp[92:], box.NextTrackID)
nn, err = w.Write(tmp[:])
n = int64(nn)
}
buf := make([]byte, boxsize)
if _, err := io.ReadFull(r, buf); err != nil {
return 0, err
}
n := 0
if fullbox.Version == 1 {
mvhd.Creation_time = binary.BigEndian.Uint64(buf[n:])
n += 8
mvhd.Modification_time = binary.BigEndian.Uint64(buf[n:])
n += 8
mvhd.Timescale = binary.BigEndian.Uint32(buf[n:])
n += 4
mvhd.Duration = binary.BigEndian.Uint64(buf[n:])
n += 8
} else {
mvhd.Creation_time = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
mvhd.Modification_time = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
mvhd.Timescale = binary.BigEndian.Uint32(buf[n:])
n += 4
mvhd.Duration = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
}
mvhd.Rate = binary.BigEndian.Uint32(buf[n:])
n += 4
mvhd.Volume = binary.BigEndian.Uint16(buf[n:])
n += 10
for i, _ := range mvhd.Matrix {
mvhd.Matrix[i] = binary.BigEndian.Uint32(buf[n:])
n += 4
}
for i := 0; i < 6; i++ {
mvhd.Pre_defined[i] = binary.BigEndian.Uint32(buf[n:])
n += 4
}
mvhd.Next_track_ID = binary.BigEndian.Uint32(buf[n:])
return n + 4 + offset, nil
return
}
func (mvhd *MovieHeaderBox) Encode() (int, []byte) {
// Always use version 0 for better compatibility
var fullbox = NewFullBox(TypeMVHD, 0)
fullbox.Box.Size = FullBoxLen + 96 // version 0 size
offset, buf := fullbox.Encode()
func (box *MovieHeaderBox) Unmarshal(buf []byte) (IBox, error) {
var offset int
if box.Version == 1 {
box.CreationTime = binary.BigEndian.Uint64(buf[0:])
box.ModificationTime = binary.BigEndian.Uint64(buf[8:])
box.Timescale = binary.BigEndian.Uint32(buf[16:])
box.Duration = binary.BigEndian.Uint64(buf[20:])
offset = 28
} else {
box.CreationTime = uint64(binary.BigEndian.Uint32(buf[0:]))
box.ModificationTime = uint64(binary.BigEndian.Uint32(buf[4:]))
box.Timescale = binary.BigEndian.Uint32(buf[8:])
box.Duration = uint64(binary.BigEndian.Uint32(buf[12:]))
offset = 16
}
// Version 0: all 32-bit values
binary.BigEndian.PutUint32(buf[offset:], uint32(mvhd.Creation_time))
offset += 4
binary.BigEndian.PutUint32(buf[offset:], uint32(mvhd.Modification_time))
offset += 4
binary.BigEndian.PutUint32(buf[offset:], mvhd.Timescale)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], uint32(mvhd.Duration))
offset += 4
box.Rate = int32(binary.BigEndian.Uint32(buf[offset:]))
box.Volume = int16(binary.BigEndian.Uint16(buf[offset+4:]))
// skip reserved: 2 + 8 bytes
binary.BigEndian.PutUint32(buf[offset:], mvhd.Rate)
offset += 4
binary.BigEndian.PutUint16(buf[offset:], mvhd.Volume)
offset += 2
offset += 10 // reserved
for _, matrix := range mvhd.Matrix {
binary.BigEndian.PutUint32(buf[offset:], matrix)
offset += 16
for i := 0; i < 9; i++ {
box.Matrix[i] = int32(binary.BigEndian.Uint32(buf[offset:]))
offset += 4
}
// skip pre_defined: 24 bytes
box.NextTrackID = binary.BigEndian.Uint32(buf[offset+24:])
offset += 24 // pre-defined
binary.BigEndian.PutUint32(buf[offset:], mvhd.Next_track_ID)
return offset + 4, buf
return box, nil
}
func MakeMvhdBox(trackid uint32, duration uint32) []byte {
mvhd := NewMovieHeaderBox()
mvhd.Next_track_ID = trackid
mvhd.Duration = uint64(duration)
_, mvhdbox := mvhd.Encode()
return mvhdbox
func init() {
RegisterBox[*MovieHeaderBox](TypeMVHD)
}

View File

@@ -1,6 +1,7 @@
package box
import (
"bytes"
"encoding/binary"
"encoding/hex"
"io"
@@ -16,47 +17,101 @@ const (
// PsshBox - Protection System Specific Header Box
// Defined in ISO/IEC 23001-7 Section 8.1
type PsshBox struct {
FullBox
SystemID [16]byte
KIDs [][16]byte
Data []byte
}
func (pssh *PsshBox) Decode(r io.Reader, basebox *BasicBox) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return
func CreatePsshBox(systemID [16]byte, kids [][16]byte, data []byte) *PsshBox {
version := uint8(0)
size := uint32(FullBoxLen + 16 + 4 + len(data))
if len(kids) > 0 {
version = 1
size += 4 + uint32(len(kids)*16)
}
buf := make([]byte, basebox.Size-FullBoxLen)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
return &PsshBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypePSSH,
size: size,
},
Version: version,
Flags: [3]byte{0, 0, 0},
},
SystemID: systemID,
KIDs: kids,
Data: data,
}
}
func (box *PsshBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
buffers := make([][]byte, 0, 3+len(box.KIDs))
buffers = append(buffers, box.SystemID[:])
if box.Version > 0 {
binary.BigEndian.PutUint32(tmp[:], uint32(len(box.KIDs)))
buffers = append(buffers, tmp[:])
for _, kid := range box.KIDs {
buffers = append(buffers, kid[:])
}
}
binary.BigEndian.PutUint32(tmp[:], uint32(len(box.Data)))
buffers = append(buffers, tmp[:], box.Data)
nn, err := io.Copy(w, io.MultiReader(bytes.NewReader(box.SystemID[:]), bytes.NewReader(tmp[:]), bytes.NewReader(box.Data)))
return int64(nn), err
}
func (box *PsshBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 20 {
return nil, io.ErrShortBuffer
}
n := 0
copy(pssh.SystemID[:], buf[n:n+16])
copy(box.SystemID[:], buf[n:n+16])
n += 16
if fullbox.Version > 0 {
if box.Version > 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
kidCount := binary.BigEndian.Uint32(buf[n:])
n += 4
if len(buf) < n+int(kidCount)*16 {
return nil, io.ErrShortBuffer
}
for i := uint32(0); i < kidCount; i++ {
var kid [16]byte
copy(kid[:], buf[n:n+16])
n += 16
pssh.KIDs = append(pssh.KIDs, kid)
box.KIDs = append(box.KIDs, kid)
}
}
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
dataLen := binary.BigEndian.Uint32(buf[n:])
n += 4
pssh.Data = buf[n : n+int(dataLen)]
return
if len(buf) < n+int(dataLen) {
return nil, io.ErrShortBuffer
}
box.Data = buf[n : n+int(dataLen)]
return box, nil
}
func (pssh *PsshBox) IsWidevine() bool {
return hex.EncodeToString(pssh.SystemID[:]) == UUIDWidevine
func (box *PsshBox) IsWidevine() bool {
return hex.EncodeToString(box.SystemID[:]) == UUIDWidevine
}
func (pssh *PsshBox) IsPlayReady() bool {
return hex.EncodeToString(pssh.SystemID[:]) == UUIDPlayReady
func (box *PsshBox) IsPlayReady() bool {
return hex.EncodeToString(box.SystemID[:]) == UUIDPlayReady
}
func (pssh *PsshBox) IsFairPlay() bool {
return hex.EncodeToString(pssh.SystemID[:]) == UUIDFairPlay
func (box *PsshBox) IsFairPlay() bool {
return hex.EncodeToString(box.SystemID[:]) == UUIDFairPlay
}
func init() {
RegisterBox[*PsshBox](TypePSSH)
}

View File

@@ -7,40 +7,119 @@ import (
// SaioBox - Sample Auxiliary Information Offsets Box (saiz) (in stbl or traf box)
type SaioBox struct {
FullBox
AuxInfoType string // Used for Common Encryption Scheme (4-bytes uint32 according to spec)
AuxInfoTypeParameter uint32
Offset []int64
}
func (s *SaioBox) Decode(r io.Reader, size uint32) error {
var fullbox FullBox
if _, err := fullbox.Decode(r); err != nil {
return err
func CreateSaioBox(auxInfoType string, auxInfoTypeParameter uint32, offset []int64) *SaioBox {
flags := uint32(0)
size := uint32(FullBoxLen + 4) // base size + entry_count
if len(auxInfoType) > 0 {
flags |= 0x01
size += 8 // auxInfoType(4) + auxInfoTypeParameter(4)
}
buf := make([]byte, size-12)
if _, err := io.ReadFull(r, buf); err != nil {
return err
}
var n int
flags := uint32(fullbox.Flags[0])<<16 | uint32(fullbox.Flags[1])<<8 | uint32(fullbox.Flags[2])
if flags&0x01 != 0 {
s.AuxInfoType = string(buf[n : n+4])
n += 4
s.AuxInfoTypeParameter = binary.BigEndian.Uint32(buf[n:])
n += 4
}
entryCount := binary.BigEndian.Uint32(buf[n:])
n += 4
if fullbox.Version == 0 {
for i := uint32(0); i < entryCount; i++ {
s.Offset = append(s.Offset, int64(binary.BigEndian.Uint32(buf[n:])))
n += 4
if len(offset) > 0 {
if offset[0] > 0xFFFFFFFF {
size += uint32(len(offset) * 8)
} else {
size += uint32(len(offset) * 4)
}
} else {
for i := uint32(0); i < entryCount; i++ {
s.Offset = append(s.Offset, int64(binary.BigEndian.Uint64(buf[n:])))
}
return &SaioBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSAIO,
size: size,
},
Version: 0,
Flags: [3]byte{byte(flags >> 16), byte(flags >> 8), byte(flags)},
},
AuxInfoType: auxInfoType,
AuxInfoTypeParameter: auxInfoTypeParameter,
Offset: offset,
}
}
func (box *SaioBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
if flags&0x01 != 0 {
copy(tmp[:4], []byte(box.AuxInfoType))
if _, err = w.Write(tmp[:4]); err != nil {
return
}
binary.BigEndian.PutUint32(tmp[:4], box.AuxInfoTypeParameter)
if _, err = w.Write(tmp[:4]); err != nil {
return
}
n += 8
}
binary.BigEndian.PutUint32(tmp[:4], uint32(len(box.Offset)))
if _, err = w.Write(tmp[:4]); err != nil {
return
}
n += 4
for _, offset := range box.Offset {
if box.Version == 0 {
binary.BigEndian.PutUint32(tmp[:4], uint32(offset))
if _, err = w.Write(tmp[:4]); err != nil {
return
}
n += 4
} else {
binary.BigEndian.PutUint64(tmp[:], uint64(offset))
if _, err = w.Write(tmp[:]); err != nil {
return
}
n += 8
}
}
return nil
return
}
func (box *SaioBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 4 {
return nil, io.ErrShortBuffer
}
n := 0
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
if flags&0x01 != 0 {
if len(buf) < n+8 {
return nil, io.ErrShortBuffer
}
box.AuxInfoType = string(buf[n : n+4])
n += 4
box.AuxInfoTypeParameter = binary.BigEndian.Uint32(buf[n:])
n += 4
}
entryCount := binary.BigEndian.Uint32(buf[n:])
n += 4
box.Offset = make([]int64, entryCount)
for i := uint32(0); i < entryCount; i++ {
if box.Version == 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.Offset[i] = int64(binary.BigEndian.Uint32(buf[n:]))
n += 4
} else {
if len(buf) < n+8 {
return nil, io.ErrShortBuffer
}
box.Offset[i] = int64(binary.BigEndian.Uint64(buf[n:]))
n += 8
}
}
return box, nil
}
func init() {
RegisterBox[*SaioBox](TypeSAIO)
}

View File

@@ -7,41 +7,110 @@ import (
// SaizBox - Sample Auxiliary Information Sizes Box (saiz) (in stbl or traf box)
type SaizBox struct {
FullBox
AuxInfoType string // Used for Common Encryption Scheme (4-bytes uint32 according to spec)
AuxInfoTypeParameter uint32
DefaultSampleInfoSize uint8
SampleCount uint32
SampleInfo []byte
DefaultSampleInfoSize uint8
}
func (s *SaizBox) Decode(r io.Reader, size uint32) error {
var fullbox FullBox
if _, err := fullbox.Decode(r); err != nil {
return err
func CreateSaizBox(auxInfoType string, auxInfoTypeParameter uint32, defaultSampleInfoSize uint8, sampleInfo []byte) *SaizBox {
flags := uint32(0)
size := uint32(FullBoxLen + 5) // base size + defaultSampleInfoSize(1) + sampleCount(4)
if len(auxInfoType) > 0 {
flags |= 0x01
size += 8 // auxInfoType(4) + auxInfoTypeParameter(4)
}
buf := make([]byte, size-12)
if _, err := io.ReadFull(r, buf); err != nil {
return err
if defaultSampleInfoSize == 0 {
size += uint32(len(sampleInfo))
}
var n int
flags := uint32(fullbox.Flags[0])<<16 | uint32(fullbox.Flags[1])<<8 | uint32(fullbox.Flags[2])
if flags&0x01 != 0 {
s.AuxInfoType = string(buf[n : n+4])
n += 4
s.AuxInfoTypeParameter = binary.BigEndian.Uint32(buf[n:])
n += 4
return &SaizBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSAIZ,
size: size,
},
Version: 0,
Flags: [3]byte{byte(flags >> 16), byte(flags >> 8), byte(flags)},
},
AuxInfoType: auxInfoType,
AuxInfoTypeParameter: auxInfoTypeParameter,
DefaultSampleInfoSize: defaultSampleInfoSize,
SampleCount: uint32(len(sampleInfo)),
SampleInfo: sampleInfo,
}
s.DefaultSampleInfoSize = buf[n]
n += 1
}
s.SampleCount = binary.BigEndian.Uint32(buf[n:])
func (box *SaizBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
if flags&0x01 != 0 {
copy(tmp[:4], []byte(box.AuxInfoType))
if _, err = w.Write(tmp[:4]); err != nil {
return
}
binary.BigEndian.PutUint32(tmp[:4], box.AuxInfoTypeParameter)
if _, err = w.Write(tmp[:4]); err != nil {
return
}
n += 8
}
if _, err = w.Write([]byte{box.DefaultSampleInfoSize}); err != nil {
return
}
n++
binary.BigEndian.PutUint32(tmp[:4], box.SampleCount)
if _, err = w.Write(tmp[:4]); err != nil {
return
}
n += 4
if s.DefaultSampleInfoSize == 0 {
for i := 0; i < int(s.SampleCount); i++ {
s.SampleInfo = append(s.SampleInfo, buf[n])
n += 1
if box.DefaultSampleInfoSize == 0 && len(box.SampleInfo) > 0 {
nn, err := w.Write(box.SampleInfo)
if err != nil {
return n, err
}
n += int64(nn)
}
return nil
return
}
func (box *SaizBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 5 {
return nil, io.ErrShortBuffer
}
n := 0
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
if flags&0x01 != 0 {
if len(buf) < n+8 {
return nil, io.ErrShortBuffer
}
box.AuxInfoType = string(buf[n : n+4])
n += 4
box.AuxInfoTypeParameter = binary.BigEndian.Uint32(buf[n:])
n += 4
}
box.DefaultSampleInfoSize = buf[n]
n++
box.SampleCount = binary.BigEndian.Uint32(buf[n:])
n += 4
if box.DefaultSampleInfoSize == 0 {
if len(buf) < n+int(box.SampleCount) {
return nil, io.ErrShortBuffer
}
box.SampleInfo = make([]byte, box.SampleCount)
copy(box.SampleInfo, buf[n:n+int(box.SampleCount)])
}
return box, nil
}
func init() {
RegisterBox[*SaizBox](TypeSAIZ)
}

View File

@@ -11,49 +11,119 @@ const (
// SencBox - Sample Encryption Box (senc) (in trak or traf box)
// See ISO/IEC 23001-7 Section 7.2 and CMAF specification
// Full Box + SampleCount
type SencBox struct {
FullBox
SampleCount uint32
PerSampleIVSize uint32
EntryList []SencEntry
}
func (senc *SencBox) Decode(r io.Reader, size uint32, perSampleIVSize uint8) (offset int, err error) {
var sencBox FullBox
if offset, err = sencBox.Decode(r); err != nil {
func CreateSencBox(perSampleIVSize uint32, entries []SencEntry, useSubSample bool) *SencBox {
flags := uint32(0)
if useSubSample {
flags |= UseSubsampleEncryption
}
size := uint32(FullBoxLen + 4) // base size + SampleCount
for _, entry := range entries {
size += uint32(len(entry.IV))
if useSubSample {
size += 2 // subsample count
size += uint32(len(entry.SubSamples) * 6) // each subsample is 6 bytes
}
}
return &SencBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSENC,
size: size,
},
Version: 0,
Flags: [3]byte{byte(flags >> 16), byte(flags >> 8), byte(flags)},
},
SampleCount: uint32(len(entries)),
PerSampleIVSize: perSampleIVSize,
EntryList: entries,
}
}
func (box *SencBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [6]byte
binary.BigEndian.PutUint32(tmp[:4], box.SampleCount)
if _, err = w.Write(tmp[:4]); err != nil {
return
}
senc.PerSampleIVSize = uint32(perSampleIVSize)
buf := make([]byte, size-12)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
n := 0
senc.SampleCount = binary.BigEndian.Uint32(buf[n:])
n += 4
sencFlags := uint32(sencBox.Flags[0])<<16 | uint32(sencBox.Flags[1])<<8 | uint32(sencBox.Flags[2])
n = 4
senc.EntryList = make([]SencEntry, senc.SampleCount)
for i := 0; i < int(senc.SampleCount); i++ {
senc.EntryList[i].IV = buf[n : n+int(senc.PerSampleIVSize)]
n += int(senc.PerSampleIVSize)
if sencFlags&UseSubsampleEncryption <= 0 {
continue
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
for _, entry := range box.EntryList {
if _, err = w.Write(entry.IV); err != nil {
return
}
n += int64(len(entry.IV))
subsampleCount := binary.BigEndian.Uint16(buf[n:])
n += 2
senc.EntryList[i].SubSamples = make([]SubSampleEntry, subsampleCount)
for j := uint16(0); j < subsampleCount; j++ {
senc.EntryList[i].SubSamples[j].BytesOfClearData = binary.BigEndian.Uint16(buf[n:])
if flags&UseSubsampleEncryption != 0 {
binary.BigEndian.PutUint16(tmp[:2], uint16(len(entry.SubSamples)))
if _, err = w.Write(tmp[:2]); err != nil {
return
}
n += 2
senc.EntryList[i].SubSamples[j].BytesOfProtectedData = binary.BigEndian.Uint32(buf[n:])
n += 4
for _, subsample := range entry.SubSamples {
binary.BigEndian.PutUint16(tmp[:2], subsample.BytesOfClearData)
binary.BigEndian.PutUint32(tmp[2:], subsample.BytesOfProtectedData)
if _, err = w.Write(tmp[:]); err != nil {
return
}
n += 6
}
}
}
offset += n
return
}
func (box *SencBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 4 {
return nil, io.ErrShortBuffer
}
n := 0
box.SampleCount = binary.BigEndian.Uint32(buf[n:])
n += 4
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
box.EntryList = make([]SencEntry, box.SampleCount)
for i := uint32(0); i < box.SampleCount; i++ {
if len(buf) < n+int(box.PerSampleIVSize) {
return nil, io.ErrShortBuffer
}
box.EntryList[i].IV = make([]byte, box.PerSampleIVSize)
copy(box.EntryList[i].IV, buf[n:n+int(box.PerSampleIVSize)])
n += int(box.PerSampleIVSize)
if flags&UseSubsampleEncryption != 0 {
if len(buf) < n+2 {
return nil, io.ErrShortBuffer
}
subsampleCount := binary.BigEndian.Uint16(buf[n:])
n += 2
if len(buf) < n+int(subsampleCount)*6 {
return nil, io.ErrShortBuffer
}
box.EntryList[i].SubSamples = make([]SubSampleEntry, subsampleCount)
for j := uint16(0); j < subsampleCount; j++ {
box.EntryList[i].SubSamples[j].BytesOfClearData = binary.BigEndian.Uint16(buf[n:])
n += 2
box.EntryList[i].SubSamples[j].BytesOfProtectedData = binary.BigEndian.Uint32(buf[n:])
n += 4
}
}
}
return box, nil
}
func init() {
RegisterBox[*SencBox](TypeSENC)
}

View File

@@ -39,100 +39,169 @@ type SidxEntry struct {
}
type SegmentIndexBox struct {
Box *FullBox
FullBox
ReferenceID uint32
TimeScale uint32
EarliestPresentationTime uint64
FirstOffset uint64
ReferenceCount uint16
Entrys []SidxEntry
Entries []SidxEntry
}
func NewSegmentIndexBox() *SegmentIndexBox {
return &SegmentIndexBox{
Box: NewFullBox(TypeSIDX, 1),
func CreateSegmentIndexBox(referenceID uint32, timeScale uint32, earliestPresentationTime uint64, firstOffset uint64, entries []SidxEntry) *SegmentIndexBox {
version := uint8(0)
if earliestPresentationTime > 0xFFFFFFFF || firstOffset > 0xFFFFFFFF {
version = 1
}
}
func (sidx *SegmentIndexBox) Size() uint64 {
return sidx.Box.Size() + 28 + uint64(len(sidx.Entrys)*12)
}
func (sidx *SegmentIndexBox) Decode(r io.Reader) (offset int, err error) {
if offset, err = sidx.Box.Decode(r); err != nil {
return
}
buf := make([]byte, sidx.Box.Box.Size-12)
if _, err = io.ReadFull(r, buf); err != nil {
return
}
n := 0
sidx.ReferenceID = binary.BigEndian.Uint32(buf[n:])
n += 4
sidx.TimeScale = binary.BigEndian.Uint32(buf[n:])
n += 4
if sidx.Box.Version == 0 {
sidx.EarliestPresentationTime = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
sidx.FirstOffset = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
size := uint32(FullBoxLen + 12) // base size + referenceID(4) + timeScale(4) + reserved(2) + referenceCount(2)
if version == 1 {
size += 16 // earliestPresentationTime(8) + firstOffset(8)
} else {
sidx.EarliestPresentationTime = binary.BigEndian.Uint64(buf[n:])
n += 8
sidx.FirstOffset = binary.BigEndian.Uint64(buf[n:])
size += 8 // earliestPresentationTime(4) + firstOffset(4)
}
size += uint32(len(entries) * 12) // each entry is 12 bytes
return &SegmentIndexBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSIDX,
size: size,
},
Version: version,
Flags: [3]byte{0, 0, 0},
},
ReferenceID: referenceID,
TimeScale: timeScale,
EarliestPresentationTime: earliestPresentationTime,
FirstOffset: firstOffset,
ReferenceCount: uint16(len(entries)),
Entries: entries,
}
}
func (box *SegmentIndexBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
binary.BigEndian.PutUint32(tmp[:4], box.ReferenceID)
if _, err = w.Write(tmp[:4]); err != nil {
return
}
n = 4
binary.BigEndian.PutUint32(tmp[:4], box.TimeScale)
if _, err = w.Write(tmp[:4]); err != nil {
return
}
n += 4
if box.Version == 0 {
binary.BigEndian.PutUint32(tmp[:4], uint32(box.EarliestPresentationTime))
if _, err = w.Write(tmp[:4]); err != nil {
return
}
binary.BigEndian.PutUint32(tmp[:4], uint32(box.FirstOffset))
if _, err = w.Write(tmp[:4]); err != nil {
return
}
n += 8
} else {
binary.BigEndian.PutUint64(tmp[:], box.EarliestPresentationTime)
if _, err = w.Write(tmp[:]); err != nil {
return
}
binary.BigEndian.PutUint64(tmp[:], box.FirstOffset)
if _, err = w.Write(tmp[:]); err != nil {
return
}
n += 16
}
binary.BigEndian.PutUint16(tmp[:2], 0) // reserved
if _, err = w.Write(tmp[:2]); err != nil {
return
}
n += 2
sidx.ReferenceCount = binary.BigEndian.Uint16(buf[n:])
n += 2
sidx.Entrys = make([]SidxEntry, sidx.ReferenceCount)
for i := 0; i < int(sidx.ReferenceCount); i++ {
sidx.Entrys[i].ReferenceType = buf[n] >> 7
buf[n] = buf[n] & 0x7F
sidx.Entrys[i].ReferencedSize = binary.BigEndian.Uint32(buf[n:])
n += 4
sidx.Entrys[i].SubsegmentDuration = binary.BigEndian.Uint32(buf[n:])
n += 4
sidx.Entrys[i].StartsWithSAP = buf[n] >> 7
sidx.Entrys[i].SAPType = buf[n] >> 4 & 0x07
buf[n] = buf[n] & 0x0F
sidx.Entrys[i].SAPDeltaTime = binary.BigEndian.Uint32(buf[n:])
n += 4
binary.BigEndian.PutUint16(tmp[:2], box.ReferenceCount)
if _, err = w.Write(tmp[:2]); err != nil {
return
}
n += 2
for _, entry := range box.Entries {
var entryBuf [12]byte
entryBuf[0] = entry.ReferenceType << 7
binary.BigEndian.PutUint32(entryBuf[:4], entry.ReferencedSize)
entryBuf[0] &= 0x7F
binary.BigEndian.PutUint32(entryBuf[4:], entry.SubsegmentDuration)
entryBuf[8] = entry.StartsWithSAP << 7
entryBuf[8] |= (entry.SAPType & 0x07) << 4
binary.BigEndian.PutUint32(entryBuf[8:], entry.SAPDeltaTime)
entryBuf[8] &= 0x0F
if _, err = w.Write(entryBuf[:]); err != nil {
return
}
n += 12
}
offset += 4
return
}
func (sidx *SegmentIndexBox) Encode() (int, []byte) {
sidx.Box.Box.Size = sidx.Size()
offset, boxdata := sidx.Box.Encode()
binary.BigEndian.PutUint32(boxdata[offset:], sidx.ReferenceID)
offset += 4
binary.BigEndian.PutUint32(boxdata[offset:], sidx.TimeScale)
offset += 4
if sidx.Box.Version == 0 {
binary.BigEndian.PutUint32(boxdata[offset:], uint32(sidx.EarliestPresentationTime))
offset += 4
binary.BigEndian.PutUint32(boxdata[offset:], uint32(sidx.FirstOffset))
offset += 4
func (box *SegmentIndexBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 8 {
return nil, io.ErrShortBuffer
}
n := 0
box.ReferenceID = binary.BigEndian.Uint32(buf[n:])
n += 4
box.TimeScale = binary.BigEndian.Uint32(buf[n:])
n += 4
if box.Version == 0 {
if len(buf) < n+8 {
return nil, io.ErrShortBuffer
}
box.EarliestPresentationTime = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
box.FirstOffset = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
} else {
binary.BigEndian.PutUint64(boxdata[offset:], sidx.EarliestPresentationTime)
offset += 8
binary.BigEndian.PutUint64(boxdata[offset:], sidx.FirstOffset)
offset += 8
if len(buf) < n+16 {
return nil, io.ErrShortBuffer
}
box.EarliestPresentationTime = binary.BigEndian.Uint64(buf[n:])
n += 8
box.FirstOffset = binary.BigEndian.Uint64(buf[n:])
n += 8
}
offset += 2
binary.BigEndian.PutUint16(boxdata[offset:], sidx.ReferenceCount)
offset += 2
for i := 0; i < int(sidx.ReferenceCount); i++ {
binary.BigEndian.PutUint32(boxdata[offset:], uint32(sidx.Entrys[i].ReferencedSize))
boxdata[offset] = boxdata[offset]&0x7F | sidx.Entrys[i].ReferenceType<<7
offset += 4
binary.BigEndian.PutUint32(boxdata[offset:], sidx.Entrys[i].SubsegmentDuration)
offset += 4
binary.BigEndian.PutUint32(boxdata[offset:], sidx.Entrys[i].SAPDeltaTime)
boxdata[offset] = (boxdata[offset] & 0xF0) + sidx.Entrys[i].StartsWithSAP<<7 | (sidx.Entrys[i].SAPType&0x07)<<4
offset += 4
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
return offset, boxdata
n += 2 // skip reserved
box.ReferenceCount = binary.BigEndian.Uint16(buf[n:])
n += 2
if len(buf) < n+int(box.ReferenceCount)*12 {
return nil, io.ErrShortBuffer
}
box.Entries = make([]SidxEntry, box.ReferenceCount)
for i := uint16(0); i < box.ReferenceCount; i++ {
box.Entries[i].ReferenceType = buf[n] >> 7
buf[n] = buf[n] & 0x7F
box.Entries[i].ReferencedSize = binary.BigEndian.Uint32(buf[n:])
n += 4
box.Entries[i].SubsegmentDuration = binary.BigEndian.Uint32(buf[n:])
n += 4
box.Entries[i].StartsWithSAP = buf[n] >> 7
box.Entries[i].SAPType = buf[n] >> 4 & 0x07
buf[n] = buf[n] & 0x0F
box.Entries[i].SAPDeltaTime = binary.BigEndian.Uint32(buf[n:])
n += 4
}
return box, nil
}
func init() {
RegisterBox[*SegmentIndexBox](TypeSIDX)
}

View File

@@ -6,42 +6,48 @@ import (
)
// aligned(8) class SoundMediaHeaderBox
// extends FullBox(smhd, version = 0, 0) {
// extends FullBox('smhd', version = 0, 0) {
// template int(16) balance = 0;
// const unsigned int(16) reserved = 0;
// }
type SoundMediaHeaderBox struct {
FullBox
Balance int16
}
func NewSoundMediaHeaderBox() *SoundMediaHeaderBox {
return &SoundMediaHeaderBox{}
}
func (smhd *SoundMediaHeaderBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return 0, err
func CreateSoundMediaHeaderBox() *SoundMediaHeaderBox {
return &SoundMediaHeaderBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSMHD,
size: uint32(FullBoxLen + 4),
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
Balance: 0,
}
buf := make([]byte, 4)
if _, err = io.ReadFull(r, buf); err != nil {
return
}
func (box *SoundMediaHeaderBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
binary.BigEndian.PutUint16(tmp[0:], uint16(box.Balance))
// reserved already zeroed
nn, err := w.Write(tmp[:])
n = int64(nn)
return
}
func (box *SoundMediaHeaderBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 4 {
return nil, io.ErrShortBuffer
}
smhd.Balance = int16(binary.BigEndian.Uint16(buf[:]))
return 4, nil
box.Balance = int16(binary.BigEndian.Uint16(buf[0:]))
return box, nil
}
func (smhd *SoundMediaHeaderBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeSMHD, 0)
fullbox.Box.Size = FullBoxLen + 4
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint16(buf[offset:], uint16(smhd.Balance))
return offset + 2, buf
}
func MakeSmhdBox() []byte {
smhd := NewSoundMediaHeaderBox()
_, smhdbox := smhd.Encode()
return smhdbox
func init() {
RegisterBox[*SoundMediaHeaderBox](TypeSMHD)
}

View File

@@ -1,10 +1,64 @@
package box
import (
"bytes"
"io"
)
type SampleTable struct {
STTS *TimeToSampleBox
CTTS *CompositionOffsetBox
STSC *SampleToChunkBox
STSZ *SampleSizeBox
STCO *ChunkOffsetBox
STSS *SyncSampleBox
STSD *STSDBox
STTS *STTSBox
CTTS *CTTSBox
STSC *STSCBox
STSZ *STSZBox
STSS *STSSBox
STCO *STCOBox
}
type SampleTableBox struct {
BaseBox
SampleTable
}
func (stbl *SampleTableBox) WriteTo(w io.Writer) (n int64, err error) {
return WriteTo(w, stbl.STSD, stbl.STTS, stbl.CTTS, stbl.STSC, stbl.STSZ, stbl.STSS, stbl.STCO)
}
func (stbl *SampleTableBox) Unmarshal(buf []byte) (IBox, error) {
r := bytes.NewReader(buf)
for {
box, err := ReadFrom(r)
if err != nil {
break
}
switch box := box.(type) {
case *STSDBox:
stbl.STSD = box
case *STTSBox:
stbl.STTS = box
case *CTTSBox:
stbl.CTTS = box
case *STCOBox:
stbl.STCO = box
case *CO64Box:
co64 := STCOBox(*box)
stbl.STCO = &co64
case *STSCBox:
stbl.STSC = box
case *STSZBox:
stbl.STSZ = box
case *STSSBox:
stbl.STSS = box
}
}
return stbl, nil
}
func init() {
RegisterBox[*SampleTableBox](TypeSTBL)
}

View File

@@ -3,98 +3,125 @@ package box
import (
"encoding/binary"
"io"
"net"
)
// aligned(8) class ChunkOffsetBox
// extends FullBox(stco, version = 0, 0) {
// extends FullBox('stco', version = 0, 0) {
// unsigned int(32) entry_count;
// for (i=1; i <= entry_count; i++) {
// unsigned int(32) chunk_offset;
// }
// }
// aligned(8) class ChunkLargeOffsetBox
// extends FullBox(co64, version = 0, 0) {
// extends FullBox('co64', version = 0, 0) {
// unsigned int(32) entry_count;
// for (i=1; i <= entry_count; i++) {
// unsigned int(64) chunk_offset;
// }
// }
type ChunkOffsetBox []uint64
type STCOBox struct {
FullBox
Entries []uint64
}
func (stco *ChunkOffsetBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return
type CO64Box STCOBox
func CreateSTCOBox(entries []uint64) *STCOBox {
return &STCOBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSTCO,
size: uint32(FullBoxLen + 4 + len(entries)*4),
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
Entries: entries,
}
tmp := make([]byte, 4)
if _, err = io.ReadFull(r, tmp); err != nil {
return
}
func CreateCO64Box(entries []uint64) *CO64Box {
return &CO64Box{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeCO64,
size: uint32(FullBoxLen + 4 + len(entries)*8),
},
},
Entries: entries,
}
offset = 8
l := binary.BigEndian.Uint32(tmp)
*stco = make([]uint64, l)
buf := make([]byte, l*4)
if _, err = io.ReadFull(r, buf); err != nil {
return
}
func (box *STCOBox) WriteTo(w io.Writer) (n int64, err error) {
buf := make([]byte, 4+len(box.Entries)*4)
// Write entry count
binary.BigEndian.PutUint32(buf[:4], uint32(len(box.Entries)))
// Write entries
for i, chunkOffset := range box.Entries {
binary.BigEndian.PutUint32(buf[4+i*4:], uint32(chunkOffset))
}
idx := 0
for i := 0; i < int(l); i++ {
(*stco)[i] = uint64(binary.BigEndian.Uint32(buf[idx:]))
_, err = w.Write(buf)
return int64(len(buf)), err
}
func (box *CO64Box) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
buffers := make(net.Buffers, 0, len(box.Entries)+1)
// Write entry count
binary.BigEndian.PutUint32(tmp[:], uint32(len(box.Entries)))
buffers = append(buffers, tmp[:])
// Write entries
for _, chunkOffset := range box.Entries {
binary.BigEndian.PutUint64(tmp[:], chunkOffset)
buffers = append(buffers, tmp[:])
}
return buffers.WriteTo(w)
}
func (box *STCOBox) Unmarshal(buf []byte) (IBox, error) {
entryCount := binary.BigEndian.Uint32(buf[:4])
box.Entries = make([]uint64, entryCount)
if len(buf) < 4+int(entryCount)*4 {
return nil, io.ErrShortBuffer
}
idx := 4
for i := 0; i < int(entryCount); i++ {
box.Entries[i] = uint64(binary.BigEndian.Uint32(buf[idx:]))
idx += 4
}
offset += idx
return
return box, nil
}
func (stco ChunkOffsetBox) Encode() (int, []byte) {
var fullbox *FullBox
l := len(stco)
if stco[l-1] > 0xFFFFFFFF {
fullbox = NewFullBox(TypeCO64, 0)
fullbox.Box.Size = FullBoxLen + 4 + 8*uint64(l)
} else {
fullbox = NewFullBox(TypeSTCO, 0)
fullbox.Box.Size = FullBoxLen + 4 + 4*uint64(l)
}
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], uint32(l))
offset += 4
for i := 0; i < int(l); i++ {
if fullbox.Box.Type == TypeCO64 {
binary.BigEndian.PutUint64(buf[offset:], uint64(stco[i]))
offset += 8
} else {
binary.BigEndian.PutUint32(buf[offset:], uint32(stco[i]))
offset += 4
}
}
return offset, buf
}
func (box *CO64Box) Unmarshal(buf []byte) (IBox, error) {
entryCount := binary.BigEndian.Uint32(buf[:4])
box.Entries = make([]uint64, entryCount)
type ChunkLargeOffsetBox ChunkOffsetBox
if len(buf) < 4+int(entryCount)*8 {
return nil, io.ErrShortBuffer
}
func (co64 *ChunkLargeOffsetBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return
}
tmp := make([]byte, 4)
if _, err = io.ReadFull(r, tmp); err != nil {
return
}
offset = 8
l := binary.BigEndian.Uint32(tmp)
*co64 = make([]uint64, l)
buf := make([]byte, l*8)
if _, err = io.ReadFull(r, buf); err != nil {
return
}
idx := 0
for i := 0; i < int(l); i++ {
(*co64)[i] = binary.BigEndian.Uint64(buf[idx:])
idx := 4
for i := 0; i < int(entryCount); i++ {
box.Entries[i] = binary.BigEndian.Uint64(buf[idx:])
idx += 8
}
offset += idx
return
return box, nil
}
func init() {
RegisterBox[*STCOBox](TypeSTCO)
RegisterBox[*CO64Box](TypeCO64)
}

View File

@@ -5,7 +5,7 @@ import (
"io"
)
// aligned(8) class SampleToChunkBox extends FullBox(stsc, version = 0, 0) {
// aligned(8) class SampleToChunkBox extends FullBox('stsc', version = 0, 0) {
// unsigned int(32) entry_count;
// for (i=1; i <= entry_count; i++) {
// unsigned int(32) first_chunk;
@@ -14,59 +14,61 @@ import (
// }
// }
type SampleToChunkBox []STSCEntry
func NewSampleToChunkBox() *SampleToChunkBox {
return &SampleToChunkBox{}
type STSCBox struct {
FullBox
Entries []STSCEntry
}
func (stsc SampleToChunkBox) Size() uint64 {
return FullBoxLen + 4 + 12*uint64(len(stsc))
func CreateSTSCBox(entries []STSCEntry) *STSCBox {
return &STSCBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSTSC,
size: uint32(FullBoxLen + 4 + len(entries)*12),
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
Entries: entries,
}
}
func (stsc *SampleToChunkBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return
func (box *STSCBox) WriteTo(w io.Writer) (n int64, err error) {
buf := make([]byte, 4+len(box.Entries)*12)
// Write entry count
binary.BigEndian.PutUint32(buf[:4], uint32(len(box.Entries)))
// Write entries
for i, entry := range box.Entries {
binary.BigEndian.PutUint32(buf[4+i*12:], entry.FirstChunk)
binary.BigEndian.PutUint32(buf[4+i*12+4:], entry.SamplesPerChunk)
binary.BigEndian.PutUint32(buf[4+i*12+8:], entry.SampleDescriptionIndex)
}
tmp := make([]byte, 4)
if _, err = io.ReadFull(r, tmp); err != nil {
return
_, err = w.Write(buf)
return int64(len(buf)), err
}
func (box *STSCBox) Unmarshal(buf []byte) (IBox, error) {
entryCount := binary.BigEndian.Uint32(buf[:4])
box.Entries = make([]STSCEntry, entryCount)
if len(buf) < 4+int(entryCount)*12 {
return nil, io.ErrShortBuffer
}
l := binary.BigEndian.Uint32(tmp)
*stsc = make([]STSCEntry, l)
buf := make([]byte, l*12)
if _, err = io.ReadFull(r, buf); err != nil {
return
}
offset = 8
idx := 0
for i := 0; i < int(l); i++ {
entry := &(*stsc)[i]
entry.FirstChunk = binary.BigEndian.Uint32(buf[idx:])
idx := 4
for i := 0; i < int(entryCount); i++ {
box.Entries[i].FirstChunk = binary.BigEndian.Uint32(buf[idx:])
idx += 4
entry.SamplesPerChunk = binary.BigEndian.Uint32(buf[idx:])
box.Entries[i].SamplesPerChunk = binary.BigEndian.Uint32(buf[idx:])
idx += 4
entry.SampleDescriptionIndex = binary.BigEndian.Uint32(buf[idx:])
box.Entries[i].SampleDescriptionIndex = binary.BigEndian.Uint32(buf[idx:])
idx += 4
}
offset += idx
return
return box, nil
}
func (stsc SampleToChunkBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeSTSC, 0)
fullbox.Box.Size = stsc.Size()
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], uint32(len(stsc)))
offset += 4
for _, entry := range stsc {
binary.BigEndian.PutUint32(buf[offset:], entry.FirstChunk)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], entry.SamplesPerChunk)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], entry.SampleDescriptionIndex)
offset += 4
}
return offset, buf
func init() {
RegisterBox[*STSCBox](TypeSTSC)
}

View File

@@ -1,6 +1,7 @@
package box
import (
"bytes"
"encoding/binary"
"io"
)
@@ -11,39 +12,8 @@ import (
// }
type SampleEntry struct {
Type [4]byte
data_reference_index uint16
}
func NewSampleEntry(format [4]byte) *SampleEntry {
return &SampleEntry{
Type: format,
data_reference_index: 1,
}
}
func (entry *SampleEntry) Size() uint64 {
return BasicBoxLen + 8
}
func (entry *SampleEntry) Decode(r io.Reader) (offset int, err error) {
buf := make([]byte, 8)
if _, err = io.ReadFull(r, buf); err != nil {
return
}
offset = 6
entry.data_reference_index = binary.BigEndian.Uint16(buf[offset:])
offset += 2
return
}
func (entry *SampleEntry) Encode(size uint64) (int, []byte) {
offset, buf := (&BasicBox{Type: entry.Type, Size: size}).Encode()
offset += 6
binary.BigEndian.PutUint16(buf[offset:], entry.data_reference_index)
offset += 2
return offset, buf
BaseBox
DataReferenceIndex uint16
}
// class HintSampleEntry() extends SampleEntry (protocol) {
@@ -51,8 +21,9 @@ func (entry *SampleEntry) Encode(size uint64) (int, []byte) {
// }
type HintSampleEntry struct {
Entry *SampleEntry
Data byte
SampleEntry
Data []byte
}
// class AudioSampleEntry(codingname) extends SampleEntry (codingname){
@@ -65,56 +36,87 @@ type HintSampleEntry struct {
// }
type AudioSampleEntry struct {
*SampleEntry
SampleEntry
Version uint16 // ffmpeg mov.c mov_parse_stsd_audio
ChannelCount uint16
SampleSize uint16
Samplerate uint32
ExtraData IBox
}
func NewAudioSampleEntry(format [4]byte) *AudioSampleEntry {
func (s *SampleEntry) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
binary.BigEndian.PutUint16(tmp[6:], s.DataReferenceIndex)
_, err = w.Write(tmp[:])
return 8, err
}
func (s *SampleEntry) Unmarshal(buf []byte) {
s.DataReferenceIndex = binary.BigEndian.Uint16(buf[6:])
}
func CreateAudioSampleEntry(codecName BoxType, channelCount uint16, sampleSize uint16, samplerate uint32, extraData IBox) *AudioSampleEntry {
size := 28 + BasicBoxLen
if extraData != nil {
size += int(extraData.Size())
}
return &AudioSampleEntry{
SampleEntry: NewSampleEntry(format),
SampleEntry: SampleEntry{
BaseBox: BaseBox{
typ: codecName,
size: uint32(size),
},
DataReferenceIndex: 1,
},
Version: 0,
ChannelCount: channelCount,
SampleSize: sampleSize,
Samplerate: samplerate,
ExtraData: extraData,
}
}
func (entry *AudioSampleEntry) Decode(r io.Reader) (offset int, err error) {
if _, err = entry.SampleEntry.Decode(r); err != nil {
func (audio *AudioSampleEntry) WriteTo(w io.Writer) (n int64, err error) {
n, err = audio.SampleEntry.WriteTo(w)
if err != nil {
return
}
buf := make([]byte, 20)
if _, err = io.ReadFull(r, buf); err != nil {
return
var buf [20]byte
binary.BigEndian.PutUint16(buf[8:], audio.Version)
binary.BigEndian.PutUint16(buf[10:], audio.ChannelCount)
binary.BigEndian.PutUint16(buf[12:], audio.SampleSize)
binary.BigEndian.PutUint32(buf[16:], audio.Samplerate<<16)
_, err = w.Write(buf[:])
n += 20
var nn int64
if audio.ExtraData != nil {
nn, err = WriteTo(w, audio.ExtraData)
if err != nil {
return
}
n += nn
}
offset = 0
entry.Version = binary.BigEndian.Uint16(buf[offset:])
offset = 8
entry.ChannelCount = binary.BigEndian.Uint16(buf[offset:])
offset += 2
entry.SampleSize = binary.BigEndian.Uint16(buf[offset:])
offset += 2
offset += 4
entry.Samplerate = binary.BigEndian.Uint32(buf[offset:])
entry.Samplerate = entry.Samplerate >> 16
offset += 4
return
}
func (entry *AudioSampleEntry) Size() uint64 {
return entry.SampleEntry.Size() + 20
}
func (audio *AudioSampleEntry) Unmarshal(buf []byte) (IBox, error) {
audio.SampleEntry.Unmarshal(buf)
buf = buf[8:]
audio.Version = binary.BigEndian.Uint16(buf[0:])
audio.ChannelCount = binary.BigEndian.Uint16(buf[8:])
audio.SampleSize = binary.BigEndian.Uint16(buf[10:])
func (entry *AudioSampleEntry) Encode(size uint64) (int, []byte) {
offset, buf := entry.SampleEntry.Encode(size)
offset += 8
binary.BigEndian.PutUint16(buf[offset:], entry.ChannelCount)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], entry.SampleSize)
offset += 2
offset += 4
binary.BigEndian.PutUint32(buf[offset:], entry.Samplerate<<16)
offset += 4
return offset, buf
audio.Samplerate = binary.BigEndian.Uint32(buf[16:]) >> 16
if len(buf) > 20 {
box, err := ReadFrom(bytes.NewReader(buf[20:]))
if err != nil {
return nil, err
}
audio.ExtraData = box
}
return audio, nil
}
// class VisualSampleEntry(codingname) extends SampleEntry (codingname){
@@ -136,71 +138,88 @@ func (entry *AudioSampleEntry) Encode(size uint64) (int, []byte) {
// }
type VisualSampleEntry struct {
*SampleEntry
SampleEntry
Width, Height uint16
horizresolution, vertresolution uint32
frame_count uint16
compressorname [32]byte
Horizresolution, Vertresolution uint32
FrameCount uint16
Compressorname [32]byte
Depth uint16
ExtraData IBox
}
func NewVisualSampleEntry(format [4]byte) *VisualSampleEntry {
func CreateVisualSampleEntry(codecName BoxType, width, height uint16, extraData IBox) *VisualSampleEntry {
size := 78 + BasicBoxLen
if extraData != nil {
size += int(extraData.Size())
}
return &VisualSampleEntry{
SampleEntry: NewSampleEntry(format),
horizresolution: 0x00480000,
vertresolution: 0x00480000,
frame_count: 1,
SampleEntry: SampleEntry{
BaseBox: BaseBox{
typ: codecName,
size: uint32(size),
},
DataReferenceIndex: 1,
},
Width: width,
Height: height,
Horizresolution: 0x00480000,
Vertresolution: 0x00480000,
FrameCount: 1,
Depth: 0x0018,
ExtraData: extraData,
}
}
func (entry *VisualSampleEntry) Size() uint64 {
return entry.SampleEntry.Size() + 70
}
func (entry *VisualSampleEntry) Decode(r io.Reader) (offset int, err error) {
if _, err = entry.SampleEntry.Decode(r); err != nil {
return 0, err
}
buf := make([]byte, 70)
if _, err = io.ReadFull(r, buf); err != nil {
func (visual *VisualSampleEntry) WriteTo(w io.Writer) (n int64, err error) {
n, err = visual.SampleEntry.WriteTo(w)
if err != nil {
return
}
offset = 16
entry.Width = binary.BigEndian.Uint16(buf[offset:])
offset += 2
entry.Height = binary.BigEndian.Uint16(buf[offset:])
offset += 2
entry.horizresolution = binary.BigEndian.Uint32(buf[offset:])
offset += 4
entry.vertresolution = binary.BigEndian.Uint32(buf[offset:])
offset += 8
entry.frame_count = binary.BigEndian.Uint16(buf[offset:])
offset += 2
copy(entry.compressorname[:], buf[offset:offset+32])
offset += 32
offset += 4
var buf [70]byte // 16(pre_defined) + 2(width) + 2(height) + 4(horiz) + 4(vert) + 4(reserved) + 2(frame) + 32(compressor) + 2(depth) + 2(pre_defined)
binary.BigEndian.PutUint16(buf[16:], visual.Width)
binary.BigEndian.PutUint16(buf[18:], visual.Height)
binary.BigEndian.PutUint32(buf[20:], visual.Horizresolution)
binary.BigEndian.PutUint32(buf[24:], visual.Vertresolution)
binary.BigEndian.PutUint16(buf[32:], visual.FrameCount)
copy(buf[34:66], visual.Compressorname[:])
binary.BigEndian.PutUint16(buf[66:], visual.Depth)
binary.BigEndian.PutUint16(buf[68:], 0xFFFF) // pre_defined = -1
_, err = w.Write(buf[:])
n += 70
var nn int64
if visual.ExtraData != nil {
nn, err = WriteTo(w, visual.ExtraData)
if err != nil {
return
}
n += nn
}
return
}
func (entry *VisualSampleEntry) Encode(size uint64) (int, []byte) {
offset, buf := entry.SampleEntry.Encode(size)
offset += 16
binary.BigEndian.PutUint16(buf[offset:], entry.Width)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], entry.Height)
offset += 2
binary.BigEndian.PutUint32(buf[offset:], entry.horizresolution)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], entry.vertresolution)
offset += 8
binary.BigEndian.PutUint16(buf[offset:], entry.frame_count)
offset += 2
copy(buf[offset:offset+32], entry.compressorname[:])
offset += 32
binary.BigEndian.PutUint16(buf[offset:], 0x0018)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], 0xFFFF)
offset += 2
return offset, buf
func (visual *VisualSampleEntry) Unmarshal(buf []byte) (IBox, error) {
visual.SampleEntry.Unmarshal(buf)
buf = buf[24:] // Skip 8 bytes from SampleEntry + 16 bytes pre_defined
visual.Width = binary.BigEndian.Uint16(buf[0:])
visual.Height = binary.BigEndian.Uint16(buf[2:])
visual.Horizresolution = binary.BigEndian.Uint32(buf[4:])
visual.Vertresolution = binary.BigEndian.Uint32(buf[8:])
visual.FrameCount = binary.BigEndian.Uint16(buf[16:])
copy(visual.Compressorname[:], buf[18:50])
visual.Depth = binary.BigEndian.Uint16(buf[50:])
if len(buf) > 52 {
box, err := ReadFrom(bytes.NewReader(buf[52:]))
if err != nil {
return nil, err
}
visual.ExtraData = box
}
return visual, nil
}
// aligned(8) class SampleDescriptionBox (unsigned int(32) handler_type) extends FullBox('stsd', 0, 0){
@@ -208,16 +227,16 @@ func (entry *VisualSampleEntry) Encode(size uint64) (int, []byte) {
// unsigned int(32) entry_count;
// for (i = 1 ; i <= entry_count ; i++){
// switch (handler_type){
// case soun: // for audio tracks
// case 'soun': // for audio tracks
// AudioSampleEntry();
// break;
// case vide: // for video tracks
// case 'vide': // for video tracks
// VisualSampleEntry();
// break;
// case hint: // Hint track
// case 'hint': // Hint track
// HintSampleEntry();
// break;
// case meta: // Metadata track
// case 'meta': // Metadata track
// MetadataSampleEntry();
// break;
// }
@@ -231,50 +250,82 @@ const (
SAMPLE_VIDEO
)
type SampleDescriptionBox uint32
type STSDBox struct {
FullBox
Entries []IBox
}
func (stsd *SampleDescriptionBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
func CreateSTSDBox(entries ...IBox) *STSDBox {
childSize := 0
for _, entry := range entries {
childSize += int(entry.Size())
}
return &STSDBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSTSD,
size: uint32(FullBoxLen + 4 + childSize),
},
},
Entries: entries,
}
}
func (stsd *STSDBox) Unmarshal(buf []byte) (IBox, error) {
stsd.Entries = make([]IBox, 0, binary.BigEndian.Uint32(buf))
r := bytes.NewReader(buf[4:])
for {
box, err := ReadFrom(r)
if err != nil {
break
}
stsd.Entries = append(stsd.Entries, box)
}
return stsd, nil
}
func (stsd *STSDBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
var nn int64
binary.BigEndian.PutUint32(tmp[:], uint32(len(stsd.Entries)))
_, err = w.Write(tmp[:])
if err != nil {
return
}
buf := make([]byte, 4)
if _, err = io.ReadFull(r, buf); err != nil {
n += 4
for _, entry := range stsd.Entries {
nn, err = WriteTo(w, entry)
if err != nil {
return
}
n += nn
}
return int64(n), nil
}
func (h *HintSampleEntry) Unmarshal(buf []byte) (IBox, error) {
h.SampleEntry.Unmarshal(buf)
h.Data = buf[8:]
return h, nil
}
func (h *HintSampleEntry) WriteTo(w io.Writer) (n int64, err error) {
var offset int64
offset, err = h.SampleEntry.WriteTo(w)
if err != nil {
return
}
offset += 4
*stsd = SampleDescriptionBox(binary.BigEndian.Uint32(buf))
return
_, err = w.Write(h.Data)
return offset + int64(len(h.Data)), err
}
func (entry SampleDescriptionBox) Encode(size uint64) (int, []byte) {
fullbox := FullBox{Box: NewBasicBox(TypeSTSD)}
fullbox.Box.Size = size
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], uint32(entry))
offset += 4
return offset, buf
}
func MakeAvcCBox(extraData []byte) []byte {
offset, boxdata := (&BasicBox{Type: TypeAVCC, Size: BasicBoxLen + uint64(len(extraData))}).Encode()
copy(boxdata[offset:], extraData)
return boxdata
}
func MakeHvcCBox(extraData []byte) []byte {
offset, boxdata := (&BasicBox{Type: TypeHVCC, Size: BasicBoxLen + uint64(len(extraData))}).Encode()
copy(boxdata[offset:], extraData)
return boxdata
}
func MakeEsdsBox(tid uint32, cid MP4_CODEC_TYPE, extraData []byte) []byte {
esd := makeESDescriptor(uint16(tid), cid, extraData)
esds := FullBox{Box: NewBasicBox(TypeESDS), Version: 0}
esds.Box.Size = esds.Size() + uint64(len(esd))
offset, esdsBox := esds.Encode()
copy(esdsBox[offset:], esd)
return esdsBox
func init() {
RegisterBox[*STSDBox](TypeSTSD)
RegisterBox[*AudioSampleEntry](TypeMP4A, TypeULAW, TypeALAW, TypeOPUS, TypeENCA)
RegisterBox[*VisualSampleEntry](TypeAVC1, TypeHVC1, TypeHEV1, TypeENCV)
RegisterBox[*HintSampleEntry](TypeHINT)
RegisterBox[*DataBox](TypeAVCC, TypeHVCC)
// RegisterBox[*MetadataSampleEntry](TypeMETA)
}
//ffmpeg mov_write_wave_tag

View File

@@ -5,7 +5,7 @@ import (
"io"
)
// aligned(8) class SyncSampleBox extends FullBox(stss, version = 0, 0) {
// aligned(8) class SyncSampleBox extends FullBox('stss', version = 0, 0) {
// unsigned int(32) entry_count;
// int i;
// for (i=0; i < entry_count; i++) {
@@ -13,41 +13,47 @@ import (
// }
// }
type SyncSampleBox []uint32
func (stss SyncSampleBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeSTSS, 0)
fullbox.Box.Size = FullBoxLen + 4 + 4*uint64(len(stss))
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], uint32(len(stss)))
offset += 4
for _, sampleNumber := range stss {
binary.BigEndian.PutUint32(buf[offset:], sampleNumber)
offset += 4
}
return offset, buf
type STSSBox struct {
FullBox
Entries []uint32
}
func (stss *SyncSampleBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return
func CreateSTSSBox(entries []uint32) *STSSBox {
return &STSSBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSTSS,
size: uint32(FullBoxLen + 4 + len(entries)*4),
},
},
Entries: entries,
}
tmp := make([]byte, 4)
if _, err = io.ReadFull(r, tmp); err != nil {
return
}
func (box *STSSBox) WriteTo(w io.Writer) (n int64, err error) {
buf := make([]byte, 4*(len(box.Entries)+1))
// Write entry count
binary.BigEndian.PutUint32(buf[:4], uint32(len(box.Entries)))
// Write entries
for i, sampleNumber := range box.Entries {
binary.BigEndian.PutUint32(buf[4+i*4:], sampleNumber)
}
offset = 8
entryCount := binary.BigEndian.Uint32(tmp[:])
buf := make([]byte, entryCount*4)
if _, err = io.ReadFull(r, buf); err != nil {
return
}
idx := 0
for range entryCount {
*stss = append(*stss, binary.BigEndian.Uint32(buf[idx:]))
_, err = w.Write(buf)
return int64(len(buf)), err
}
func (box *STSSBox) Unmarshal(buf []byte) (IBox, error) {
entryCount := binary.BigEndian.Uint32(buf[:4])
box.Entries = make([]uint32, entryCount)
idx := 4
for i := 0; i < int(entryCount); i++ {
box.Entries[i] = binary.BigEndian.Uint32(buf[idx:])
idx += 4
}
offset += idx
return
return box, nil
}
func init() {
RegisterBox[*STSSBox](TypeSTSS)
}

View File

@@ -5,7 +5,7 @@ import (
"io"
)
// aligned(8) class SampleSizeBox extends FullBox(stsz, version = 0, 0) {
// aligned(8) class SampleSizeBox extends FullBox('stsz', version = 0, 0) {
// unsigned int(32) sample_size;
// unsigned int(32) sample_count;
// if (sample_size==0) {
@@ -15,61 +15,66 @@ import (
// }
// }
type SampleSizeBox struct {
type STSZBox struct {
FullBox
SampleSize uint32
SampleCount uint32
EntrySizelist []uint32
}
func (stsz *SampleSizeBox) Size() uint64 {
if stsz.SampleSize == 0 {
return FullBoxLen + 8 + 4*uint64(stsz.SampleCount)
} else {
return FullBoxLen + 8
func CreateSTSZBox(sampleSize uint32, entrySizelist []uint32) *STSZBox {
return &STSZBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSTSZ,
size: uint32(FullBoxLen + 8 + len(entrySizelist)*4),
},
},
SampleSize: sampleSize,
SampleCount: uint32(len(entrySizelist)),
EntrySizelist: entrySizelist,
}
}
func (stsz *SampleSizeBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return
}
tmp := make([]byte, 8)
if _, err = io.ReadFull(r, tmp); err != nil {
return
}
offset = 12
stsz.SampleSize = binary.BigEndian.Uint32(tmp[:])
stsz.SampleCount = binary.BigEndian.Uint32(tmp[4:])
if stsz.SampleSize == 0 {
buf := make([]byte, stsz.SampleCount*4)
if _, err = io.ReadFull(r, buf); err != nil {
return
func (box *STSZBox) WriteTo(w io.Writer) (n int64, err error) {
buf := make([]byte, 8+len(box.EntrySizelist)*4)
// Write sample size
binary.BigEndian.PutUint32(buf[:4], box.SampleSize)
// Write sample count
binary.BigEndian.PutUint32(buf[4:8], box.SampleCount)
// Write entry sizes if sample size is 0
if box.SampleSize == 0 {
for i, size := range box.EntrySizelist {
binary.BigEndian.PutUint32(buf[8+i*4:], size)
}
idx := 0
stsz.EntrySizelist = make([]uint32, stsz.SampleCount)
for i := 0; i < int(stsz.SampleCount); i++ {
stsz.EntrySizelist[i] = binary.BigEndian.Uint32(buf[idx:])
}
_, err = w.Write(buf)
return int64(len(buf)), err
}
func (box *STSZBox) Unmarshal(buf []byte) (IBox, error) {
box.SampleSize = binary.BigEndian.Uint32(buf[:4])
box.SampleCount = binary.BigEndian.Uint32(buf[4:8])
if box.SampleSize == 0 {
if len(buf) < 8+int(box.SampleCount)*4 {
return nil, io.ErrShortBuffer
}
box.EntrySizelist = make([]uint32, box.SampleCount)
idx := 8
for i := 0; i < int(box.SampleCount); i++ {
box.EntrySizelist[i] = binary.BigEndian.Uint32(buf[idx:])
idx += 4
}
offset += idx
}
return
return box, nil
}
func (stsz *SampleSizeBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeSTSZ, 0)
fullbox.Box.Size = stsz.Size()
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], stsz.SampleSize)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], stsz.SampleCount)
offset += 4
if stsz.SampleSize == 0 {
for i := 0; i < int(stsz.SampleCount); i++ {
binary.BigEndian.PutUint32(buf[offset:], stsz.EntrySizelist[i])
offset += 4
}
}
return offset, buf
func init() {
RegisterBox[*STSZBox](TypeSTSZ)
}

View File

@@ -5,7 +5,7 @@ import (
"io"
)
// aligned(8) class TimeToSampleBox extends FullBox(stts, version = 0, 0) {
// aligned(8) class TimeToSampleBox extends FullBox('stts', version = 0, 0) {
// unsigned int(32) entry_count;
// int i;
// for (i=0; i < entry_count; i++) {
@@ -14,50 +14,60 @@ import (
// }
// }
type TimeToSampleBox []STTSEntry
func (stts TimeToSampleBox) Size() uint64 {
return FullBoxLen + 4 + 8*uint64(len(stts))
type STTSBox struct {
FullBox
Entries []STTSEntry
}
func (stts *TimeToSampleBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return
func CreateSTTSBox(entries []STTSEntry) *STTSBox {
return &STTSBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeSTTS,
size: uint32(FullBoxLen + 4 + len(entries)*8),
},
Version: 0,
Flags: [3]byte{0, 0, 0},
},
Entries: entries,
}
entryCountBuf := make([]byte, 4)
if _, err = io.ReadFull(r, entryCountBuf); err != nil {
return
}
func (box *STTSBox) WriteTo(w io.Writer) (n int64, err error) {
buf := make([]byte, 4+len(box.Entries)*8)
// Write entry count
binary.BigEndian.PutUint32(buf[:4], uint32(len(box.Entries)))
// Write entries
for i, entry := range box.Entries {
binary.BigEndian.PutUint32(buf[4+i*8:], entry.SampleCount)
binary.BigEndian.PutUint32(buf[4+i*8+4:], entry.SampleDelta)
}
offset = 8
l := binary.BigEndian.Uint32(entryCountBuf)
*stts = make([]STTSEntry, l)
buf := make([]byte, l*8)
if _, err = io.ReadFull(r, buf); err != nil {
return
_, err = w.Write(buf)
return int64(len(buf)), err
}
func (box *STTSBox) Unmarshal(buf []byte) (IBox, error) {
entryCount := binary.BigEndian.Uint32(buf[:4])
box.Entries = make([]STTSEntry, entryCount)
if len(buf) < 4+int(entryCount)*8 {
return nil, io.ErrShortBuffer
}
idx := 0
for i := 0; i < int(l); i++ {
(*stts)[i].SampleCount = binary.BigEndian.Uint32(buf[idx:])
idx := 4
for i := 0; i < int(entryCount); i++ {
box.Entries[i].SampleCount = binary.BigEndian.Uint32(buf[idx:])
idx += 4
(*stts)[i].SampleDelta = binary.BigEndian.Uint32(buf[idx:])
box.Entries[i].SampleDelta = binary.BigEndian.Uint32(buf[idx:])
idx += 4
}
offset += idx
return
return box, nil
}
func (stts TimeToSampleBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeSTTS, 0)
fullbox.Box.Size = stts.Size()
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], uint32(len(stts)))
offset += 4
for _, entry := range stts {
binary.BigEndian.PutUint32(buf[offset:], entry.SampleCount)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], entry.SampleDelta)
offset += 4
}
return offset, buf
func init() {
RegisterBox[*STTSBox](TypeSTTS)
}

View File

@@ -5,7 +5,7 @@ import (
"io"
)
// aligned(8) class TrackFragmentBaseMediaDecodeTimeBox extends FullBox(tfdt, version, 0) {
// aligned(8) class TrackFragmentBaseMediaDecodeTimeBox extends FullBox('tfdt', version, 0) {
// if (version==1) {
// unsigned int(64) baseMediaDecodeTime;
// } else { // version==0
@@ -14,53 +14,58 @@ import (
// }
type TrackFragmentBaseMediaDecodeTimeBox struct {
FullBox
BaseMediaDecodeTime uint64
version uint8
}
func NewTrackFragmentBaseMediaDecodeTimeBox(fragStart uint64) *TrackFragmentBaseMediaDecodeTimeBox {
func CreateTrackFragmentBaseMediaDecodeTimeBox(baseMediaDecodeTime uint64) *TrackFragmentBaseMediaDecodeTimeBox {
version := uint8(0)
if fragStart > 0xFFFFFFFF {
size := uint32(FullBoxLen + 4)
if baseMediaDecodeTime > 0xFFFFFFFF {
version = 1
size = uint32(FullBoxLen + 8)
}
return &TrackFragmentBaseMediaDecodeTimeBox{
version: version,
BaseMediaDecodeTime: fragStart,
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeTFDT,
size: size,
},
Version: version,
Flags: [3]byte{0, 0, 0},
},
BaseMediaDecodeTime: baseMediaDecodeTime,
}
}
func (tfdt *TrackFragmentBaseMediaDecodeTimeBox) Size() uint64 {
if tfdt.version == 1 {
return FullBoxLen + 8 // 8 bytes for base_media_decode_time
}
return FullBoxLen + 4 // 4 bytes for base_media_decode_time
}
func (tfdt *TrackFragmentBaseMediaDecodeTimeBox) Decode(r io.Reader, size uint32) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return
}
buf := make([]byte, size-12)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
if fullbox.Version == 1 {
tfdt.BaseMediaDecodeTime = binary.BigEndian.Uint64(buf)
offset += 8
func (box *TrackFragmentBaseMediaDecodeTimeBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
if box.Version == 1 {
binary.BigEndian.PutUint64(tmp[:], box.BaseMediaDecodeTime)
nn, err := w.Write(tmp[:8])
return int64(nn), err
} else {
tfdt.BaseMediaDecodeTime = uint64(binary.BigEndian.Uint32(buf))
offset += 4
binary.BigEndian.PutUint32(tmp[:], uint32(box.BaseMediaDecodeTime))
nn, err := w.Write(tmp[:4])
return int64(nn), err
}
return
}
func (tfdt *TrackFragmentBaseMediaDecodeTimeBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeTFDT, 1)
tfdt.version = fullbox.Version
fullbox.Box.Size = tfdt.Size()
offset, boxdata := fullbox.Encode()
binary.BigEndian.PutUint64(boxdata[offset:], tfdt.BaseMediaDecodeTime)
return offset + 8, boxdata
func (box *TrackFragmentBaseMediaDecodeTimeBox) Unmarshal(buf []byte) (IBox, error) {
if box.Version == 1 {
if len(buf) < 8 {
return nil, io.ErrShortBuffer
}
box.BaseMediaDecodeTime = binary.BigEndian.Uint64(buf[:8])
} else {
if len(buf) < 4 {
return nil, io.ErrShortBuffer
}
box.BaseMediaDecodeTime = uint64(binary.BigEndian.Uint32(buf[:4]))
}
return box, nil
}
func init() {
RegisterBox[*TrackFragmentBaseMediaDecodeTimeBox](TypeTFDT)
}

View File

@@ -5,7 +5,7 @@ import (
"io"
)
// aligned(8) class TrackFragmentHeaderBox extends FullBox(tfhd, 0, tf_flags){
// aligned(8) class TrackFragmentHeaderBox extends FullBox('tfhd', 0, tf_flags){
// unsigned int(32) track_ID;
// // all the following are optional fields
// unsigned int(64) base_data_offset;
@@ -38,7 +38,8 @@ const (
)
type TrackFragmentHeaderBox struct {
Track_ID uint32
FullBox
TrackID uint32
BaseDataOffset uint64
SampleDescriptionIndex uint32
DefaultSampleDuration uint32
@@ -46,107 +47,151 @@ type TrackFragmentHeaderBox struct {
DefaultSampleFlags uint32
}
func NewTrackFragmentHeaderBox(trackid uint32) *TrackFragmentHeaderBox {
func CreateTrackFragmentHeaderBox(trackID uint32, flags uint32) *TrackFragmentHeaderBox {
size := uint32(FullBoxLen + 4) // base size + track_ID
if flags&TF_FLAG_BASE_DATA_OFFSET_PRESENT != 0 {
size += 8
}
if flags&TF_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT != 0 {
size += 4
}
if flags&TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT != 0 {
size += 4
}
if flags&TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT != 0 {
size += 4
}
if flags&TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT != 0 {
size += 4
}
return &TrackFragmentHeaderBox{
Track_ID: trackid,
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeTFHD,
size: size,
},
Version: 0,
Flags: [3]byte{byte(flags >> 16), byte(flags >> 8), byte(flags)},
},
TrackID: trackID,
SampleDescriptionIndex: 1,
}
}
func (tfhd *TrackFragmentHeaderBox) Size(thfdFlags uint32) uint64 {
n := uint64(FullBoxLen)
n += 4
if thfdFlags&TF_FLAG_BASE_DATA_OFFSET_PRESENT > 0 && thfdFlags&TF_FLAG_DEFAULT_BASE_IS_MOOF == 0 {
n += 8
func (box *TrackFragmentHeaderBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
binary.BigEndian.PutUint32(tmp[:], box.TrackID)
nn, err := w.Write(tmp[:4])
if err != nil {
return int64(nn), err
}
if thfdFlags&TF_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT > 0 {
n += 4
}
if thfdFlags&TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT > 0 {
n += 4
}
if thfdFlags&TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT > 0 {
n += 4
}
if thfdFlags&TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT > 0 {
n += 4
}
return n
}
n = int64(nn)
func (tfhd *TrackFragmentHeaderBox) Decode(r io.Reader, size uint32, moofOffset uint64) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return
}
buf := make([]byte, size-12)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
n := 0
tfhd.Track_ID = binary.BigEndian.Uint32(buf[n:])
n += 4
tfhdFlags := uint32(fullbox.Flags[0])<<16 | uint32(fullbox.Flags[1])<<8 | uint32(fullbox.Flags[2])
if tfhdFlags&uint32(TF_FLAG_BASE_DATA_OFFSET_PRESENT) > 0 {
tfhd.BaseDataOffset = binary.BigEndian.Uint64(buf[n:])
n += 8
}
if tfhdFlags&uint32(TF_FLAG_DEFAULT_BASE_IS_MOOF) > 0 {
tfhd.BaseDataOffset = moofOffset
} else {
//TODO,In some cases, it is wrong
tfhd.BaseDataOffset = moofOffset
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
if flags&TF_FLAG_BASE_DATA_OFFSET_PRESENT != 0 {
binary.BigEndian.PutUint64(tmp[:], box.BaseDataOffset)
nn, err = w.Write(tmp[:8])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
if tfhdFlags&uint32(TF_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT) > 0 {
tfhd.SampleDescriptionIndex = binary.BigEndian.Uint32(buf[n:])
n += 4
if flags&TF_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT != 0 {
binary.BigEndian.PutUint32(tmp[:], box.SampleDescriptionIndex)
nn, err = w.Write(tmp[:4])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
if tfhdFlags&uint32(TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT) > 0 {
tfhd.DefaultSampleDuration = binary.BigEndian.Uint32(buf[n:])
n += 4
if flags&TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT != 0 {
binary.BigEndian.PutUint32(tmp[:], box.DefaultSampleDuration)
nn, err = w.Write(tmp[:4])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
if tfhdFlags&uint32(TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT) > 0 {
tfhd.DefaultSampleSize = binary.BigEndian.Uint32(buf[n:])
n += 4
if flags&TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT != 0 {
binary.BigEndian.PutUint32(tmp[:], box.DefaultSampleSize)
nn, err = w.Write(tmp[:4])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
if tfhdFlags&uint32(TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT) > 0 {
tfhd.DefaultSampleFlags = binary.BigEndian.Uint32(buf[n:])
n += 4
if flags&TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT != 0 {
binary.BigEndian.PutUint32(tmp[:], box.DefaultSampleFlags)
nn, err = w.Write(tmp[:4])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
offset += n
return
}
func (tfhd *TrackFragmentHeaderBox) Encode(tFfFlags uint32) (int, []byte) {
fullbox := NewFullBox(TypeTFHD, 0)
fullbox.Flags[0] = byte(tFfFlags >> 16)
fullbox.Flags[1] = byte(tFfFlags >> 8)
fullbox.Flags[2] = byte(tFfFlags)
fullbox.Box.Size = tfhd.Size(tFfFlags)
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint32(buf[offset:], tfhd.Track_ID)
offset += 4
thfdFlags := uint32(fullbox.Flags[0])<<16 | uint32(fullbox.Flags[1])<<8 | uint32(fullbox.Flags[2])
if thfdFlags&uint32(TF_FLAG_BASE_DATA_OFFSET_PRESENT) > 0 && thfdFlags&uint32(TF_FLAG_DEFAULT_BASE_IS_MOOF) == 0 {
binary.BigEndian.PutUint64(buf[offset:], tfhd.BaseDataOffset)
offset += 8
func (box *TrackFragmentHeaderBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 4 {
return nil, io.ErrShortBuffer
}
if thfdFlags&uint32(TF_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT) > 0 {
binary.BigEndian.PutUint32(buf[offset:], tfhd.SampleDescriptionIndex)
offset += 4
n := 0
box.TrackID = binary.BigEndian.Uint32(buf[n:])
n += 4
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
if flags&TF_FLAG_BASE_DATA_OFFSET_PRESENT != 0 {
if len(buf) < n+8 {
return nil, io.ErrShortBuffer
}
box.BaseDataOffset = binary.BigEndian.Uint64(buf[n:])
n += 8
}
if thfdFlags&uint32(TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT) > 0 {
binary.BigEndian.PutUint32(buf[offset:], tfhd.DefaultSampleDuration)
offset += 4
if flags&TF_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.SampleDescriptionIndex = binary.BigEndian.Uint32(buf[n:])
n += 4
}
if thfdFlags&uint32(TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT) > 0 {
binary.BigEndian.PutUint32(buf[offset:], tfhd.DefaultSampleSize)
offset += 4
if flags&TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.DefaultSampleDuration = binary.BigEndian.Uint32(buf[n:])
n += 4
}
if thfdFlags&uint32(TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT) > 0 {
binary.BigEndian.PutUint32(buf[offset:], tfhd.DefaultSampleFlags)
offset += 4
if flags&TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.DefaultSampleSize = binary.BigEndian.Uint32(buf[n:])
n += 4
}
return offset, buf
if flags&TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.DefaultSampleFlags = binary.BigEndian.Uint32(buf[n:])
n += 4
}
return box, nil
}
func init() {
RegisterBox[*TrackFragmentHeaderBox](TypeTFHD)
}

View File

@@ -28,109 +28,186 @@ import (
// }
type TrackFragmentRandomAccessBox struct {
Box *FullBox
FullBox
TrackID uint32
LengthSizeOfTrafNum uint8
LengthSizeOfTrunNum uint8
LengthSizeOfSampleNum uint8
FragEntrys []FragEntry
Entries []TFRAEntry
}
func NewTrackFragmentRandomAccessBox(trackid uint32) *TrackFragmentRandomAccessBox {
func CreateTrackFragmentRandomAccessBox(trackID uint32, entries []TFRAEntry) *TrackFragmentRandomAccessBox {
return &TrackFragmentRandomAccessBox{
Box: NewFullBox(TypeTFRA, 1),
TrackID: trackid,
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeTFRA,
size: uint32(FullBoxLen + 8 + 4 + len(entries)*(3+8)),
},
},
TrackID: trackID,
Entries: entries,
}
}
func (tfra *TrackFragmentRandomAccessBox) Size() uint64 {
entrySize := 0
if tfra.Box.Version == 1 {
entrySize = 16 // 8 bytes for time + 8 bytes for moof_offset
} else {
entrySize = 8 // 4 bytes for time + 4 bytes for moof_offset
}
// Add size for traf_number, trun_number, and sample_number
entrySize += int(tfra.LengthSizeOfTrafNum + tfra.LengthSizeOfTrunNum + tfra.LengthSizeOfSampleNum + 3)
return tfra.Box.Size() + 12 + uint64(len(tfra.FragEntrys)*entrySize) // 12 = 4(track_id) + 4(reserved) + 4(number_of_entry)
}
func (tfra *TrackFragmentRandomAccessBox) Decode(r io.Reader) (offset int, err error) {
if offset, err = tfra.Box.Decode(r); err != nil {
func (box *TrackFragmentRandomAccessBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [12]byte
binary.BigEndian.PutUint32(tmp[:4], box.TrackID)
tmp[7] = box.LengthSizeOfTrafNum<<6 | box.LengthSizeOfTrunNum<<4 | box.LengthSizeOfSampleNum<<2
binary.BigEndian.PutUint32(tmp[8:], uint32(len(box.Entries)))
nn, err := w.Write(tmp[:])
if err != nil {
return
}
n = int64(nn)
needSize := tfra.Box.Box.Size - 12
buf := make([]byte, needSize)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
n := 0
tfra.TrackID = binary.BigEndian.Uint32(buf[n:])
n += 4
tfra.LengthSizeOfTrafNum = (buf[n+3] >> 4) & 0x03
tfra.LengthSizeOfTrunNum = (buf[n+3] >> 2) & 0x03
tfra.LengthSizeOfSampleNum = buf[n+3] & 0x03
n += 4
tfra.FragEntrys = make([]FragEntry, binary.BigEndian.Uint32(buf[n:]))
n += 4
for i := range tfra.FragEntrys {
frag := &tfra.FragEntrys[i]
if tfra.Box.Version == 1 {
frag.Time = binary.BigEndian.Uint64(buf[n:])
n += 8
frag.MoofOffset = binary.BigEndian.Uint64(buf[n:])
n += 8
for _, entry := range box.Entries {
if box.Version == 1 {
binary.BigEndian.PutUint64(tmp[:], entry.Time)
nn, err = w.Write(tmp[:8])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
binary.BigEndian.PutUint64(tmp[:], entry.MoofOffset)
nn, err = w.Write(tmp[:8])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
} else {
frag.Time = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
frag.MoofOffset = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
binary.BigEndian.PutUint32(tmp[:4], uint32(entry.Time))
binary.BigEndian.PutUint32(tmp[4:8], uint32(entry.MoofOffset))
nn, err = w.Write(tmp[:8])
if err != nil {
return
}
n += int64(nn)
}
n += int(tfra.LengthSizeOfTrafNum + tfra.LengthSizeOfTrunNum + tfra.LengthSizeOfSampleNum + 3)
trafSize := box.LengthSizeOfTrafNum + 1
trunSize := box.LengthSizeOfTrunNum + 1
sampleSize := box.LengthSizeOfSampleNum + 1
switch trafSize {
case 4:
binary.BigEndian.PutUint32(tmp[:], entry.TrafNumber)
case 3:
binary.BigEndian.PutUint32(tmp[:], entry.TrafNumber&0x00FFFFFF)
case 2:
binary.BigEndian.PutUint16(tmp[:], uint16(entry.TrafNumber))
case 1:
tmp[0] = uint8(entry.TrafNumber)
}
switch trunSize {
case 4:
binary.BigEndian.PutUint32(tmp[trafSize:], entry.TrunNumber)
case 3:
binary.BigEndian.PutUint32(tmp[trafSize:], entry.TrunNumber&0x00FFFFFF)
case 2:
binary.BigEndian.PutUint16(tmp[trafSize:], uint16(entry.TrunNumber))
case 1:
tmp[trafSize] = uint8(entry.TrunNumber)
}
switch sampleSize {
case 4:
binary.BigEndian.PutUint32(tmp[trafSize+trunSize:], entry.SampleNumber)
case 3:
binary.BigEndian.PutUint32(tmp[trafSize+trunSize:], entry.SampleNumber&0x00FFFFFF)
case 2:
binary.BigEndian.PutUint16(tmp[trafSize+trunSize:], uint16(entry.SampleNumber))
case 1:
tmp[trafSize+trunSize] = uint8(entry.SampleNumber)
}
nn, err = w.Write(tmp[:trafSize+trunSize+sampleSize])
if err != nil {
return
}
n += int64(nn)
}
offset += 4
return
}
func (tfra *TrackFragmentRandomAccessBox) Encode() (int, []byte) {
tfra.Box.Box.Size = tfra.Size()
offset, boxdata := tfra.Box.Encode()
binary.BigEndian.PutUint32(boxdata[offset:], tfra.TrackID)
offset += 4
// Pack length size fields into the reserved uint32
lengthSizeFlags := uint32(tfra.LengthSizeOfTrafNum&0x03)<<4 |
uint32(tfra.LengthSizeOfTrunNum&0x03)<<2 |
uint32(tfra.LengthSizeOfSampleNum&0x03)
binary.BigEndian.PutUint32(boxdata[offset:], lengthSizeFlags)
offset += 4
binary.BigEndian.PutUint32(boxdata[offset:], uint32(len(tfra.FragEntrys)))
offset += 4
for _, frag := range tfra.FragEntrys {
if tfra.Box.Version == 1 {
binary.BigEndian.PutUint64(boxdata[offset:], frag.Time)
offset += 8
binary.BigEndian.PutUint64(boxdata[offset:], frag.MoofOffset)
offset += 8
func (box *TrackFragmentRandomAccessBox) Unmarshal(buf []byte) (IBox, error) {
box.TrackID = binary.BigEndian.Uint32(buf[:4])
flags := buf[7]
box.LengthSizeOfTrafNum = (flags >> 6) & 0x03
box.LengthSizeOfTrunNum = (flags >> 4) & 0x03
box.LengthSizeOfSampleNum = (flags >> 2) & 0x03
entryCount := binary.BigEndian.Uint32(buf[8:])
n := 12
box.Entries = make([]TFRAEntry, entryCount)
for i := uint32(0); i < entryCount; i++ {
if box.Version == 1 {
if len(buf) < n+16 {
return nil, io.ErrShortBuffer
}
box.Entries[i].Time = binary.BigEndian.Uint64(buf[n:])
n += 8
box.Entries[i].MoofOffset = binary.BigEndian.Uint64(buf[n:])
n += 8
} else {
binary.BigEndian.PutUint32(boxdata[offset:], uint32(frag.Time))
offset += 4
binary.BigEndian.PutUint32(boxdata[offset:], uint32(frag.MoofOffset))
offset += 4
if len(buf) < n+8 {
return nil, io.ErrShortBuffer
}
box.Entries[i].Time = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
box.Entries[i].MoofOffset = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
}
// Write traf_number, trun_number, and sample_number based on length sizes
for i := uint8(0); i < tfra.LengthSizeOfTrafNum+1; i++ {
boxdata[offset] = 1
offset++
trafSize := box.LengthSizeOfTrafNum + 1
trunSize := box.LengthSizeOfTrunNum + 1
sampleSize := box.LengthSizeOfSampleNum + 1
if len(buf) < n+int(trafSize+trunSize+sampleSize) {
return nil, io.ErrShortBuffer
}
for i := uint8(0); i < tfra.LengthSizeOfTrunNum+1; i++ {
boxdata[offset] = 1
offset++
switch trafSize {
case 4:
box.Entries[i].TrafNumber = binary.BigEndian.Uint32(buf[n:])
case 3:
box.Entries[i].TrafNumber = binary.BigEndian.Uint32(buf[n:]) & 0x00FFFFFF
case 2:
box.Entries[i].TrafNumber = uint32(binary.BigEndian.Uint16(buf[n:]))
case 1:
box.Entries[i].TrafNumber = uint32(buf[n])
}
for i := uint8(0); i < tfra.LengthSizeOfSampleNum+1; i++ {
boxdata[offset] = 1
offset++
n += int(trafSize)
switch trunSize {
case 4:
box.Entries[i].TrunNumber = binary.BigEndian.Uint32(buf[n:])
case 3:
box.Entries[i].TrunNumber = binary.BigEndian.Uint32(buf[n:]) & 0x00FFFFFF
case 2:
box.Entries[i].TrunNumber = uint32(binary.BigEndian.Uint16(buf[n:]))
case 1:
box.Entries[i].TrunNumber = uint32(buf[n])
}
n += int(trunSize)
switch sampleSize {
case 4:
box.Entries[i].SampleNumber = binary.BigEndian.Uint32(buf[n:])
case 3:
box.Entries[i].SampleNumber = binary.BigEndian.Uint32(buf[n:]) & 0x00FFFFFF
case 2:
box.Entries[i].SampleNumber = uint32(binary.BigEndian.Uint16(buf[n:]))
case 1:
box.Entries[i].SampleNumber = uint32(buf[n])
}
n += int(sampleSize)
}
return offset, boxdata
return box, nil
}
func init() {
RegisterBox[*TrackFragmentRandomAccessBox](TypeTFRA)
}

View File

@@ -9,7 +9,7 @@ import (
)
// aligned(8) class TrackHeaderBox
// extends FullBox(tkhd, version, flags){
// extends FullBox('tkhd', version, flags){
// if (version==1) {
// unsigned int(64) creation_time;
// unsigned int(64) modification_time;
@@ -36,118 +36,124 @@ import (
// }
type TrackHeaderBox struct {
Creation_time uint64
Modification_time uint64
Track_ID uint32
Duration uint64
Layer uint16
Alternate_group uint16
Volume uint16
Matrix [9]uint32
Width uint32
Height uint32
FullBox
CreationTime uint64
ModificationTime uint64
TrackID uint32
Duration uint64
Layer uint16
AlternateGroup uint16
Volume uint16
Matrix [9]uint32
Width uint32
Height uint32
}
func NewTrackHeaderBox() *TrackHeaderBox {
func CreateTrackHeaderBox(trackID uint32, duration uint64, width, height uint32) *TrackHeaderBox {
_, offset := time.Now().Zone()
now := uint64(time.Now().Unix() + int64(offset) + 0x7C25B080)
version := util.Conditional[uint8](duration > 0xFFFFFFFF, 1, 0)
return &TrackHeaderBox{
Creation_time: uint64(time.Now().Unix() + int64(offset) + 0x7C25B080),
Modification_time: uint64(time.Now().Unix() + int64(offset) + 0x7C25B080),
Layer: 0,
Alternate_group: 0,
Matrix: [9]uint32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeTKHD,
size: util.Conditional[uint32](version == 1, 92, 80) + FullBoxLen,
},
Version: version,
Flags: [3]byte{0, 0, 3}, // Track_enabled | Track_in_movie
},
CreationTime: now,
ModificationTime: now,
TrackID: trackID,
Duration: duration,
Layer: 0,
AlternateGroup: 0,
Matrix: [9]uint32{0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000},
Width: width,
Height: height,
}
}
func (tkhd *TrackHeaderBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return 0, err
}
boxsize := 0
if fullbox.Version == 0 {
boxsize = 80
func (box *TrackHeaderBox) WriteTo(w io.Writer) (n int64, err error) {
var data []byte
if box.Version == 1 {
data = make([]byte, 92)
binary.BigEndian.PutUint64(data[0:], box.CreationTime)
binary.BigEndian.PutUint64(data[8:], box.ModificationTime)
binary.BigEndian.PutUint32(data[16:], box.TrackID)
binary.BigEndian.PutUint64(data[24:], box.Duration)
// 32-40 reserved
} else {
boxsize = 92
data = make([]byte, 80)
binary.BigEndian.PutUint32(data[0:], uint32(box.CreationTime))
binary.BigEndian.PutUint32(data[4:], uint32(box.ModificationTime))
binary.BigEndian.PutUint32(data[8:], box.TrackID)
binary.BigEndian.PutUint32(data[16:], uint32(box.Duration))
// 20-28 reserved
}
buf := make([]byte, boxsize)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
offset := util.Conditional[int](box.Version == 1, 32, 20)
// 8 bytes reserved already zeroed
offset += 8
binary.BigEndian.PutUint16(data[offset:], box.Layer)
binary.BigEndian.PutUint16(data[offset+2:], box.AlternateGroup)
binary.BigEndian.PutUint16(data[offset+4:], box.Volume)
// 2 bytes reserved already zeroed
offset += 8
for i, m := range box.Matrix {
binary.BigEndian.PutUint32(data[offset+i*4:], m)
}
n := 0
if fullbox.Version == 1 {
tkhd.Creation_time = binary.BigEndian.Uint64(buf[n:])
n += 8
tkhd.Modification_time = binary.BigEndian.Uint64(buf[n:])
n += 8
tkhd.Track_ID = binary.BigEndian.Uint32(buf[n:])
n += 8
tkhd.Duration = binary.BigEndian.Uint64(buf[n:])
n += 8
} else {
tkhd.Creation_time = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
tkhd.Modification_time = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
tkhd.Track_ID = binary.BigEndian.Uint32(buf[n:])
n += 8
tkhd.Duration = uint64(binary.BigEndian.Uint32(buf[n:]))
n += 4
}
n += 8
tkhd.Layer = binary.BigEndian.Uint16(buf[n:])
n += 2
tkhd.Alternate_group = binary.BigEndian.Uint16(buf[n:])
n += 2
tkhd.Volume = binary.BigEndian.Uint16(buf[n:])
n += 4
for i := 0; i < 9; i++ {
tkhd.Matrix[i] = binary.BigEndian.Uint32(buf[n:])
n += 4
}
tkhd.Width = binary.BigEndian.Uint32(buf[n:])
tkhd.Height = binary.BigEndian.Uint32(buf[n+4:])
offset += n + 8
offset += 36
binary.BigEndian.PutUint32(data[offset:], box.Width)
binary.BigEndian.PutUint32(data[offset+4:], box.Height)
nn, err := w.Write(data)
n = int64(nn)
return
}
func (tkhd *TrackHeaderBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeTKHD, util.Conditional[uint8](tkhd.Duration > 0xFFFFFFFF, 1, 0))
fullbox.Flags[2] = 0x03 //Track_enabled | Track_in_movie
fullbox.Box.Size = util.Conditional[uint64](fullbox.Version == 1, 92, 80) + FullBoxLen
offset, buf := fullbox.Encode()
if fullbox.Version == 1 {
binary.BigEndian.PutUint64(buf[offset:], tkhd.Creation_time)
offset += 8
binary.BigEndian.PutUint64(buf[offset:], tkhd.Creation_time)
offset += 8
binary.BigEndian.PutUint32(buf[offset:], tkhd.Track_ID)
offset += 8
binary.BigEndian.PutUint64(buf[offset:], tkhd.Duration)
offset += 8
func (box *TrackHeaderBox) Unmarshal(buf []byte) (IBox, error) {
if box.Version == 1 {
if len(buf) < 92 {
return nil, io.ErrShortBuffer
}
box.CreationTime = binary.BigEndian.Uint64(buf[0:])
box.ModificationTime = binary.BigEndian.Uint64(buf[8:])
box.TrackID = binary.BigEndian.Uint32(buf[16:])
box.Duration = binary.BigEndian.Uint64(buf[24:])
buf = buf[32:]
} else {
binary.BigEndian.PutUint32(buf[offset:], uint32(tkhd.Creation_time))
offset += 4
binary.BigEndian.PutUint32(buf[offset:], uint32(tkhd.Creation_time))
offset += 4
binary.BigEndian.PutUint32(buf[offset:], tkhd.Track_ID)
offset += 8
binary.BigEndian.PutUint32(buf[offset:], uint32(tkhd.Duration))
offset += 4
if len(buf) < 80 {
return nil, io.ErrShortBuffer
}
box.CreationTime = uint64(binary.BigEndian.Uint32(buf[0:]))
box.ModificationTime = uint64(binary.BigEndian.Uint32(buf[4:]))
box.TrackID = binary.BigEndian.Uint32(buf[8:])
box.Duration = uint64(binary.BigEndian.Uint32(buf[16:]))
buf = buf[20:]
}
offset += 8
binary.BigEndian.PutUint16(buf[offset:], tkhd.Layer)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], tkhd.Alternate_group)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], tkhd.Volume)
offset += 4
for i, _ := range tkhd.Matrix {
binary.BigEndian.PutUint32(buf[offset:], tkhd.Matrix[i])
offset += 4
buf = buf[8:] // skip reserved
box.Layer = binary.BigEndian.Uint16(buf[0:])
box.AlternateGroup = binary.BigEndian.Uint16(buf[2:])
box.Volume = binary.BigEndian.Uint16(buf[4:])
buf = buf[8:] // skip reserved
for i := 0; i < 9; i++ {
box.Matrix[i] = binary.BigEndian.Uint32(buf[i*4:])
}
binary.BigEndian.PutUint32(buf[offset:], uint32(tkhd.Width))
offset += 4
binary.BigEndian.PutUint32(buf[offset:], uint32(tkhd.Height))
return offset + 4, buf
buf = buf[36:]
box.Width = binary.BigEndian.Uint32(buf[0:])
box.Height = binary.BigEndian.Uint32(buf[4:])
return box, nil
}
func init() {
RegisterBox[*TrackHeaderBox](TypeTKHD)
}

153
plugin/mp4/pkg/box/trak.go Normal file
View File

@@ -0,0 +1,153 @@
package box
import (
"bytes"
"io"
)
type TrakBox struct {
BaseBox
MDIA *MdiaBox
EDTS *EdtsBox
TKHD *TrackHeaderBox
}
func CreateTrakBox(mdia *MdiaBox, edts *EdtsBox, tkhd *TrackHeaderBox) *TrakBox {
size := uint32(BasicBoxLen)
if mdia != nil {
size += mdia.size
}
if edts != nil {
size += edts.size
}
if tkhd != nil {
size += tkhd.size
}
return &TrakBox{
BaseBox: BaseBox{
typ: TypeTRAK,
size: size,
},
MDIA: mdia,
EDTS: edts,
TKHD: tkhd,
}
}
func (t *TrakBox) WriteTo(w io.Writer) (n int64, err error) {
return WriteTo(w, t.MDIA, t.EDTS, t.TKHD)
}
func (t *TrakBox) Unmarshal(buf []byte) (IBox, error) {
for {
b, err := ReadFrom(bytes.NewReader(buf))
if err != nil {
return t, err
}
switch box := b.(type) {
case *MdiaBox:
t.MDIA = box
case *EdtsBox:
t.EDTS = box
case *TrackHeaderBox:
t.TKHD = box
}
}
}
// ParseSamples parses the sample table and builds the sample list
func (t *TrakBox) ParseSamples() (samplelist []Sample) {
stbl := t.MDIA.MINF.STBL
var chunkOffsets []uint64
if stbl.STCO != nil {
chunkOffsets = stbl.STCO.Entries
}
var sampleToChunks []STSCEntry
if stbl.STSC != nil {
sampleToChunks = stbl.STSC.Entries
}
var sampleCount uint32
if stbl.STSZ != nil {
sampleCount = stbl.STSZ.SampleCount
}
samplelist = make([]Sample, sampleCount)
iterator := 0
for i, chunk := range sampleToChunks {
var samplesInChunk uint32
if i < len(sampleToChunks)-1 {
samplesInChunk = sampleToChunks[i+1].FirstChunk - chunk.FirstChunk
} else {
samplesInChunk = uint32(len(chunkOffsets)) - chunk.FirstChunk + 1
}
for j := uint32(0); j < samplesInChunk; j++ {
chunkIndex := chunk.FirstChunk - 1 + j
if chunkIndex >= uint32(len(chunkOffsets)) {
break
}
for k := uint32(0); k < chunk.SamplesPerChunk; k++ {
if iterator >= len(samplelist) {
break
}
sample := &samplelist[iterator]
if stbl.STSZ != nil {
if stbl.STSZ.SampleSize != 0 {
sample.Size = int(stbl.STSZ.SampleSize)
} else {
sample.Size = int(stbl.STSZ.EntrySizelist[iterator])
}
}
sample.Offset = int64(chunkOffsets[chunkIndex])
if k > 0 {
sample.Offset = samplelist[iterator-1].Offset + int64(samplelist[iterator-1].Size)
}
iterator++
}
}
}
// Process STTS entries for timestamps
if stbl.STTS != nil {
sampleIndex := 0
timestamp := uint64(0)
for _, entry := range stbl.STTS.Entries {
for i := uint32(0); i < entry.SampleCount; i++ {
if sampleIndex >= len(samplelist) {
break
}
samplelist[sampleIndex].DTS = timestamp
samplelist[sampleIndex].PTS = timestamp
timestamp += uint64(entry.SampleDelta)
sampleIndex++
}
}
}
// Process CTTS entries for presentation timestamps
if stbl.CTTS != nil {
sampleIndex := 0
for _, entry := range stbl.CTTS.Entries {
for i := uint32(0); i < entry.SampleCount; i++ {
if sampleIndex >= len(samplelist) {
break
}
samplelist[sampleIndex].PTS = samplelist[sampleIndex].DTS + uint64(entry.SampleOffset)
sampleIndex++
}
}
}
return samplelist
}
func init() {
RegisterBox[*TrakBox](TypeTRAK)
}

View File

@@ -5,7 +5,7 @@ import (
"io"
)
// aligned(8) class TrackExtendsBox extends FullBox(trex, 0, 0){
// aligned(8) class TrackExtendsBox extends FullBox('trex', 0, 0){
// unsigned int(32) track_ID;
// unsigned int(32) default_sample_description_index;
// unsigned int(32) default_sample_duration;
@@ -14,7 +14,7 @@ import (
// }
type TrackExtendsBox struct {
Box *FullBox
FullBox
TrackID uint32
DefaultSampleDescriptionIndex uint32
DefaultSampleDuration uint32
@@ -22,52 +22,45 @@ type TrackExtendsBox struct {
DefaultSampleFlags uint32
}
func NewTrackExtendsBox(track uint32) *TrackExtendsBox {
func CreateTrackExtendsBox(trackID uint32) *TrackExtendsBox {
return &TrackExtendsBox{
Box: NewFullBox(TypeTREX, 0),
TrackID: track,
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeTREX,
size: uint32(FullBoxLen + 20),
},
},
TrackID: trackID,
}
}
func (trex *TrackExtendsBox) Size() uint64 {
return trex.Box.Size() + 20
func (box *TrackExtendsBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [20]byte
binary.BigEndian.PutUint32(tmp[0:], box.TrackID)
binary.BigEndian.PutUint32(tmp[4:], box.DefaultSampleDescriptionIndex)
binary.BigEndian.PutUint32(tmp[8:], box.DefaultSampleDuration)
binary.BigEndian.PutUint32(tmp[12:], box.DefaultSampleSize)
binary.BigEndian.PutUint32(tmp[16:], box.DefaultSampleFlags)
nn, err := w.Write(tmp[:])
n = int64(nn)
return
}
func (trex *TrackExtendsBox) Decode(r io.Reader) (offset int, err error) {
if offset, err = trex.Box.Decode(r); err != nil {
return 0, err
func (box *TrackExtendsBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 20 {
return nil, io.ErrShortBuffer
}
buf := make([]byte, 20)
if _, err := io.ReadFull(r, buf); err != nil {
return 0, err
}
n := 0
trex.TrackID = binary.BigEndian.Uint32(buf[n:])
n += 4
trex.DefaultSampleDescriptionIndex = binary.BigEndian.Uint32(buf[n:])
n += 4
trex.DefaultSampleDuration = binary.BigEndian.Uint32(buf[n:])
n += 4
trex.DefaultSampleSize = binary.BigEndian.Uint32(buf[n:])
n += 4
trex.DefaultSampleFlags = binary.BigEndian.Uint32(buf[n:])
n += 4
return offset + 20, nil
box.TrackID = binary.BigEndian.Uint32(buf[0:])
box.DefaultSampleDescriptionIndex = binary.BigEndian.Uint32(buf[4:])
box.DefaultSampleDuration = binary.BigEndian.Uint32(buf[8:])
box.DefaultSampleSize = binary.BigEndian.Uint32(buf[12:])
box.DefaultSampleFlags = binary.BigEndian.Uint32(buf[16:])
return box, nil
}
func (trex *TrackExtendsBox) Encode() (int, []byte) {
trex.Box.Box.Size = trex.Size()
offset, buf := trex.Box.Encode()
binary.BigEndian.PutUint32(buf[offset:], trex.TrackID)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], trex.DefaultSampleDescriptionIndex)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], trex.DefaultSampleDuration)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], trex.DefaultSampleSize)
offset += 4
binary.BigEndian.PutUint32(buf[offset:], trex.DefaultSampleFlags)
offset += 4
return offset, buf
func init() {
RegisterBox[*TrackExtendsBox](TypeTREX)
}

View File

@@ -35,160 +35,200 @@ const (
TR_FLAG_DATA_SAMPLE_COMPOSITION_TIME uint32 = 0x000800
)
type TrunEntry struct {
SampleDuration uint32
SampleSize uint32
SampleFlags uint32
SampleCompositionTimeOffset int32
}
type TrackRunBox struct {
FullBox
SampleCount uint32
Dataoffset int32
DataOffset int32
FirstSampleFlags uint32
EntryList []TrunEntry
Entries []TrunEntry
}
func NewTrackRunBox() *TrackRunBox {
return &TrackRunBox{}
}
func CreateTrackRunBox(flags uint32, sampleCount uint32) *TrackRunBox {
size := uint32(FullBoxLen + 4) // base size + sample_count
func (trun *TrackRunBox) Size(trunFlags uint32) uint64 {
size := uint64(8) // box header
size += 4 // version and flags
size += 4 // sample count
// data offset is always present if flag is set
if trunFlags&TR_FLAG_DATA_OFFSET != 0 {
if flags&TR_FLAG_DATA_OFFSET != 0 {
size += 4
}
if flags&TR_FLAG_DATA_FIRST_SAMPLE_FLAGS != 0 {
size += 4
}
// first sample flags is present if flag is set
if trunFlags&TR_FLAG_DATA_FIRST_SAMPLE_FLAGS != 0 {
size += 4
entrySize := uint32(0)
if flags&TR_FLAG_DATA_SAMPLE_DURATION != 0 {
entrySize += 4
}
if flags&TR_FLAG_DATA_SAMPLE_SIZE != 0 {
entrySize += 4
}
if flags&TR_FLAG_DATA_SAMPLE_FLAGS != 0 {
entrySize += 4
}
if flags&TR_FLAG_DATA_SAMPLE_COMPOSITION_TIME != 0 {
entrySize += 4
}
// calculate size for each sample entry
for i := 0; i < int(trun.SampleCount); i++ {
// sample duration is present if flag is set
if trunFlags&TR_FLAG_DATA_SAMPLE_DURATION != 0 {
size += 4
}
// sample size is present if flag is set
if trunFlags&TR_FLAG_DATA_SAMPLE_SIZE != 0 {
size += 4
}
// sample flags is present if flag is set
if trunFlags&TR_FLAG_DATA_SAMPLE_FLAGS != 0 {
size += 4
}
// sample composition time offset is present if flag is set
if trunFlags&TR_FLAG_DATA_SAMPLE_COMPOSITION_TIME != 0 {
size += 4
}
size += entrySize * sampleCount
return &TrackRunBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeTRUN,
size: size,
},
Version: 1, // Use version 1 for signed composition time offsets
Flags: [3]byte{byte(flags >> 16), byte(flags >> 8), byte(flags)},
},
SampleCount: sampleCount,
Entries: make([]TrunEntry, sampleCount),
}
return size
}
func (trun *TrackRunBox) Decode(r io.Reader, size uint32, dataOffset int32) (offset int, err error) {
var fullbox FullBox
if offset, err = fullbox.Decode(r); err != nil {
return
func (box *TrackRunBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [4]byte
binary.BigEndian.PutUint32(tmp[:], box.SampleCount)
nn, err := w.Write(tmp[:])
if err != nil {
return int64(nn), err
}
buf := make([]byte, size-12)
if _, err = io.ReadFull(r, buf); err != nil {
return
n = int64(nn)
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
if flags&TR_FLAG_DATA_OFFSET != 0 {
binary.BigEndian.PutUint32(tmp[:], uint32(box.DataOffset))
nn, err = w.Write(tmp[:])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
if flags&TR_FLAG_DATA_FIRST_SAMPLE_FLAGS != 0 {
binary.BigEndian.PutUint32(tmp[:], box.FirstSampleFlags)
nn, err = w.Write(tmp[:])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
for i := uint32(0); i < box.SampleCount; i++ {
if flags&TR_FLAG_DATA_SAMPLE_DURATION != 0 {
binary.BigEndian.PutUint32(tmp[:], box.Entries[i].SampleDuration)
nn, err = w.Write(tmp[:])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
if flags&TR_FLAG_DATA_SAMPLE_SIZE != 0 {
binary.BigEndian.PutUint32(tmp[:], box.Entries[i].SampleSize)
nn, err = w.Write(tmp[:])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
if flags&TR_FLAG_DATA_SAMPLE_FLAGS != 0 {
binary.BigEndian.PutUint32(tmp[:], box.Entries[i].SampleFlags)
nn, err = w.Write(tmp[:])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
if flags&TR_FLAG_DATA_SAMPLE_COMPOSITION_TIME != 0 {
binary.BigEndian.PutUint32(tmp[:], uint32(box.Entries[i].SampleCompositionTimeOffset))
nn, err = w.Write(tmp[:])
if err != nil {
return n + int64(nn), err
}
n += int64(nn)
}
}
return
}
func (box *TrackRunBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 4 {
return nil, io.ErrShortBuffer
}
n := 0
trun.SampleCount = binary.BigEndian.Uint32(buf[n:])
box.SampleCount = binary.BigEndian.Uint32(buf[n:])
n += 4
trunFlags := uint32(fullbox.Flags[0])<<16 | uint32(fullbox.Flags[1])<<8 | uint32(fullbox.Flags[2])
flags := uint32(box.Flags[0])<<16 | uint32(box.Flags[1])<<8 | uint32(box.Flags[2])
if trunFlags&TR_FLAG_DATA_OFFSET != 0 {
trun.Dataoffset = int32(binary.BigEndian.Uint32(buf[n:]))
n += 4
} else {
trun.Dataoffset = dataOffset
}
if trunFlags&TR_FLAG_DATA_FIRST_SAMPLE_FLAGS != 0 {
trun.FirstSampleFlags = binary.BigEndian.Uint32(buf[n:])
if flags&TR_FLAG_DATA_OFFSET != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.DataOffset = int32(binary.BigEndian.Uint32(buf[n:]))
n += 4
}
trun.EntryList = make([]TrunEntry, trun.SampleCount)
for i := 0; i < int(trun.SampleCount); i++ {
if trunFlags&TR_FLAG_DATA_SAMPLE_DURATION != 0 {
trun.EntryList[i].SampleDuration = binary.BigEndian.Uint32(buf[n:])
if flags&TR_FLAG_DATA_FIRST_SAMPLE_FLAGS != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.FirstSampleFlags = binary.BigEndian.Uint32(buf[n:])
n += 4
}
box.Entries = make([]TrunEntry, box.SampleCount)
for i := uint32(0); i < box.SampleCount; i++ {
if flags&TR_FLAG_DATA_SAMPLE_DURATION != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.Entries[i].SampleDuration = binary.BigEndian.Uint32(buf[n:])
n += 4
}
if trunFlags&TR_FLAG_DATA_SAMPLE_SIZE != 0 {
trun.EntryList[i].SampleSize = binary.BigEndian.Uint32(buf[n:])
if flags&TR_FLAG_DATA_SAMPLE_SIZE != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.Entries[i].SampleSize = binary.BigEndian.Uint32(buf[n:])
n += 4
}
if trunFlags&TR_FLAG_DATA_SAMPLE_FLAGS != 0 {
trun.EntryList[i].SampleFlags = binary.BigEndian.Uint32(buf[n:])
if flags&TR_FLAG_DATA_SAMPLE_FLAGS != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
box.Entries[i].SampleFlags = binary.BigEndian.Uint32(buf[n:])
n += 4
}
if trunFlags&TR_FLAG_DATA_SAMPLE_COMPOSITION_TIME != 0 {
if fullbox.Version == 0 {
trun.EntryList[i].SampleCompositionTimeOffset = int32(binary.BigEndian.Uint32(buf[n:]))
if flags&TR_FLAG_DATA_SAMPLE_COMPOSITION_TIME != 0 {
if len(buf) < n+4 {
return nil, io.ErrShortBuffer
}
if box.Version == 0 {
box.Entries[i].SampleCompositionTimeOffset = int32(binary.BigEndian.Uint32(buf[n:]))
} else {
trun.EntryList[i].SampleCompositionTimeOffset = int32(binary.BigEndian.Uint32(buf[n:]))
box.Entries[i].SampleCompositionTimeOffset = int32(binary.BigEndian.Uint32(buf[n:]))
}
n += 4
}
}
offset += n
return
return box, nil
}
func (trun *TrackRunBox) Encode(trunFlags uint32) (int, []byte) {
// Always use version 1 for signed composition time offsets
fullbox := NewFullBox(TypeTRUN, 1)
fullbox.Box.Size = trun.Size(trunFlags)
fullbox.Flags[0] = byte(trunFlags >> 16)
fullbox.Flags[1] = byte(trunFlags >> 8)
fullbox.Flags[2] = byte(trunFlags)
offset, buf := fullbox.Encode()
// Write sample count
binary.BigEndian.PutUint32(buf[offset:], trun.SampleCount)
offset += 4
// Write data offset if present
if trunFlags&TR_FLAG_DATA_OFFSET != 0 {
// Write data offset as int32
binary.BigEndian.PutUint32(buf[offset:], uint32(trun.Dataoffset))
offset += 4
}
// Write first sample flags if present
if trunFlags&TR_FLAG_DATA_FIRST_SAMPLE_FLAGS != 0 {
binary.BigEndian.PutUint32(buf[offset:], trun.FirstSampleFlags)
offset += 4
}
// Write sample entries in the correct order
for i := 0; i < int(trun.SampleCount); i++ {
// Write sample duration if present
if trunFlags&TR_FLAG_DATA_SAMPLE_DURATION != 0 {
binary.BigEndian.PutUint32(buf[offset:], trun.EntryList[i].SampleDuration)
offset += 4
}
// Write sample size if present
if trunFlags&TR_FLAG_DATA_SAMPLE_SIZE != 0 {
binary.BigEndian.PutUint32(buf[offset:], trun.EntryList[i].SampleSize)
offset += 4
}
// Write sample flags if present
if trunFlags&TR_FLAG_DATA_SAMPLE_FLAGS != 0 {
binary.BigEndian.PutUint32(buf[offset:], trun.EntryList[i].SampleFlags)
offset += 4
}
// Write sample composition time offset if present
if trunFlags&TR_FLAG_DATA_SAMPLE_COMPOSITION_TIME != 0 {
// Version 1 uses signed int32 for composition time offset
binary.BigEndian.PutUint32(buf[offset:], uint32(trun.EntryList[i].SampleCompositionTimeOffset))
offset += 4
}
}
return offset, buf
func init() {
RegisterBox[*TrackRunBox](TypeTRUN)
}

View File

@@ -5,65 +5,61 @@ import (
"io"
)
// Box Types: vmhd, smhd, hmhd, nmhd
// Container: Media Information Box (minf)
// Box Types: 'vmhd', 'smhd', 'hmhd', 'nmhd'
// Container: Media Information Box ('minf')
// Mandatory: Yes
// Quantity: Exactly one specific media header shall be present
// aligned(8) class VideoMediaHeaderBox
// extends FullBox(vmhd, version = 0, 1) {
// extends FullBox('vmhd', version = 0, 1) {
// template unsigned int(16) graphicsmode = 0; // copy, see below template
// unsigned int(16)[3] opcolor = {0, 0, 0};
// }
type VideoMediaHeaderBox struct {
FullBox
Graphicsmode uint16
Opcolor [3]uint16
}
func NewVideoMediaHeaderBox() *VideoMediaHeaderBox {
func CreateVideoMediaHeaderBox() *VideoMediaHeaderBox {
return &VideoMediaHeaderBox{
FullBox: FullBox{
BaseBox: BaseBox{
typ: TypeVMHD,
size: uint32(FullBoxLen + 8),
},
Version: 0,
Flags: [3]byte{0, 0, 1}, // Flags = 1
},
Graphicsmode: 0,
Opcolor: [3]uint16{0, 0, 0},
}
}
func (vmhd *VideoMediaHeaderBox) Decode(r io.Reader) (offset int, err error) {
var fullbox FullBox
if _, err = fullbox.Decode(r); err != nil {
return 0, err
}
buf := make([]byte, 8)
if _, err = io.ReadFull(r, buf); err != nil {
return 0, err
}
offset = 0
vmhd.Graphicsmode = binary.BigEndian.Uint16(buf[offset:])
vmhd.Opcolor[0] = binary.BigEndian.Uint16(buf[offset+2:])
vmhd.Opcolor[1] = binary.BigEndian.Uint16(buf[offset+4:])
vmhd.Opcolor[2] = binary.BigEndian.Uint16(buf[offset+6:])
offset += 8
func (box *VideoMediaHeaderBox) WriteTo(w io.Writer) (n int64, err error) {
var tmp [8]byte
binary.BigEndian.PutUint16(tmp[0:], box.Graphicsmode)
binary.BigEndian.PutUint16(tmp[2:], box.Opcolor[0])
binary.BigEndian.PutUint16(tmp[4:], box.Opcolor[1])
binary.BigEndian.PutUint16(tmp[6:], box.Opcolor[2])
nn, err := w.Write(tmp[:])
n = int64(nn)
return
}
func (vmhd *VideoMediaHeaderBox) Encode() (int, []byte) {
fullbox := NewFullBox(TypeVMHD, 0)
fullbox.Box.Size = FullBoxLen + 8
fullbox.Flags[2] = 1
offset, buf := fullbox.Encode()
binary.BigEndian.PutUint16(buf[offset:], vmhd.Graphicsmode)
offset += 2
binary.BigEndian.PutUint16(buf[offset:], vmhd.Opcolor[0])
offset += 2
binary.BigEndian.PutUint16(buf[offset:], vmhd.Opcolor[1])
offset += 2
binary.BigEndian.PutUint16(buf[offset:], vmhd.Opcolor[2])
offset += 2
return offset, buf
func (box *VideoMediaHeaderBox) Unmarshal(buf []byte) (IBox, error) {
if len(buf) < 8 {
return nil, io.ErrShortBuffer
}
box.Graphicsmode = binary.BigEndian.Uint16(buf[0:])
box.Opcolor[0] = binary.BigEndian.Uint16(buf[2:])
box.Opcolor[1] = binary.BigEndian.Uint16(buf[4:])
box.Opcolor[2] = binary.BigEndian.Uint16(buf[6:])
return box, nil
}
func MakeVmhdBox() []byte {
vmhd := NewVideoMediaHeaderBox()
_, vmhdbox := vmhd.Encode()
return vmhdbox
func init() {
RegisterBox[*VideoMediaHeaderBox](TypeVMHD)
}

View File

@@ -1,12 +1,12 @@
package mp4
import (
"encoding/binary"
"errors"
"io"
"slices"
"m7s.live/v5/pkg"
"m7s.live/v5/plugin/mp4/pkg/box"
. "m7s.live/v5/plugin/mp4/pkg/box"
)
@@ -36,15 +36,6 @@ type (
BytesClear uint16
BytesProtected uint32
}
Info struct {
MajorBrand [4]byte
MinorVersion uint32
CompatibleBrands [][4]byte
Duration uint32
Timescale uint32
CreateTime uint64
ModifyTime uint64
}
movchunk struct {
chunknum uint32
@@ -53,16 +44,13 @@ type (
}
Demuxer struct {
Info
reader io.ReadSeeker
mdatOffset []uint64
Tracks []*Track
ReadSampleIdx []uint32
IsFragment bool
currentTrack *Track
pssh []*PsshBox
moofOffset int64
dataOffset uint32
// pssh []*PsshBox
moov *MoovBox
QuicTime bool
}
)
@@ -73,353 +61,109 @@ func NewDemuxer(r io.ReadSeeker) *Demuxer {
}
func (d *Demuxer) Demux() (err error) {
var offset int64
var lastTrack *Track
decodeVisualSampleEntry := func() (offset int, err error) {
var encv VisualSampleEntry
encv.SampleEntry = new(SampleEntry)
_, err = encv.Decode(d.reader)
offset = int(encv.Size() - BasicBoxLen)
lastTrack.Width = uint32(encv.Width)
lastTrack.Height = uint32(encv.Height)
return
}
decodeAudioSampleEntry := func() (offset int, err error) {
var enca AudioSampleEntry
enca.SampleEntry = new(SampleEntry)
_, err = enca.Decode(d.reader)
lastTrack.ChannelCount = uint8(enca.ChannelCount)
lastTrack.SampleSize = enca.SampleSize
lastTrack.SampleRate = enca.Samplerate
offset = int(enca.Size() - BasicBoxLen)
if slices.Contains(d.Info.CompatibleBrands, [4]byte{'q', 't', ' ', ' '}) {
if enca.Version == 1 {
if _, err = io.ReadFull(d.reader, make([]byte, 16)); err != nil {
return
}
offset += 16
} else if enca.Version == 2 {
if _, err = io.ReadFull(d.reader, make([]byte, 36)); err != nil {
return
}
offset += 36
}
}
return
}
// decodeVisualSampleEntry := func() (offset int, err error) {
// var encv VisualSampleEntry
// encv.SampleEntry = new(SampleEntry)
// _, err = encv.Decode(d.reader)
// offset = int(encv.Size() - BasicBoxLen)
// lastTrack.Width = uint32(encv.Width)
// lastTrack.Height = uint32(encv.Height)
// return
// }
// decodeAudioSampleEntry := func() (offset int, err error) {
// var enca AudioSampleEntry
// enca.SampleEntry = new(SampleEntry)
// _, err = enca.Decode(d.reader)
// lastTrack.ChannelCount = uint8(enca.ChannelCount)
// lastTrack.SampleSize = enca.SampleSize
// lastTrack.SampleRate = enca.Samplerate
// offset = int(enca.Size() - BasicBoxLen)
// if slices.Contains(d.Info.CompatibleBrands, [4]byte{'q', 't', ' ', ' '}) {
// if enca.Version == 1 {
// if _, err = io.ReadFull(d.reader, make([]byte, 16)); err != nil {
// return
// }
// offset += 16
// } else if enca.Version == 2 {
// if _, err = io.ReadFull(d.reader, make([]byte, 36)); err != nil {
// return
// }
// offset += 36
// }
// }
// return
// }
for {
var basebox BasicBox
basebox.Offset = offset
var headerSize, contentSize int
var hasChild bool
headerSize, err = basebox.Decode(d.reader)
b, err := box.ReadFrom(d.reader)
if err != nil {
break
}
nextChildBoxOffset := offset + int64(headerSize)
offset = offset + int64(basebox.Size)
// if basebox.Size < BasicBoxLen {
// err = errors.New("mp4 Parser error")
// break
// }
switch basebox.Type {
case TypeFTYP:
var ftyp FileTypeBox
ftyp.Decode(d.reader, &basebox)
d.MajorBrand = ftyp.Major_brand
d.MinorVersion = ftyp.Minor_version
d.CompatibleBrands = ftyp.Compatible_brands
case TypeFREE:
var free FreeBox
free.Decode(d.reader, &basebox)
case TypeMOOV, TypeMDIA, TypeMINF, TypeSTBL:
hasChild = true
case TypeMVHD:
var mvhd MovieHeaderBox
mvhd.Decode(d.reader, &basebox)
d.CreateTime = mvhd.Creation_time
d.ModifyTime = mvhd.Modification_time
d.Duration = uint32(mvhd.Duration)
d.Timescale = mvhd.Timescale
case TypeMDAT:
d.mdatOffset = append(d.mdatOffset, uint64(basebox.Offset+BasicBoxLen))
if _, err = d.reader.Seek(offset, io.SeekStart); err != nil {
return
switch box := b.(type) {
case *FileTypeBox:
if slices.Contains(box.CompatibleBrands, [4]byte{'q', 't', ' ', ' '}) {
d.QuicTime = true
}
case TypePSSH:
var pssh PsshBox
pssh.Decode(d.reader, &basebox)
d.pssh = append(d.pssh, &pssh)
case TypeTRAK:
lastTrack = &Track{}
d.Tracks = append(d.Tracks, lastTrack)
hasChild = true
case TypeTKHD:
var tkhd TrackHeaderBox
tkhd.Decode(d.reader)
lastTrack.TrackId = tkhd.Track_ID
lastTrack.Duration = uint32(tkhd.Duration)
case TypeMDHD:
var mdhd MediaHeaderBox
mdhd.Decode(d.reader)
lastTrack.Timescale = mdhd.Timescale
case TypeHDLR:
var hdlr HandlerBox
if _, err = hdlr.Decode(d.reader, basebox.Size); err != nil {
return
case *FreeBox:
case *MediaDataBox:
case *MoovBox:
if box.MVEX != nil {
d.IsFragment = true
}
case TypeVMHD:
var vmhd VideoMediaHeaderBox
vmhd.Decode(d.reader)
case TypeSMHD:
var smhd SoundMediaHeaderBox
smhd.Decode(d.reader)
case TypeHMHD:
var hmhd HintMediaHeaderBox
hmhd.Decode(d.reader)
case TypeNMHD:
var fullbox FullBox
fullbox.Decode(d.reader)
case TypeSTSD:
var stsd SampleDescriptionBox
if contentSize, err = stsd.Decode(d.reader); err != nil {
return
}
hasChild = true
case TypeSTTS:
var stts TimeToSampleBox
stts.Decode(d.reader)
lastTrack.SampleTable.STTS = &stts
case TypeCTTS:
var ctts CompositionOffsetBox
ctts.Decode(d.reader)
lastTrack.SampleTable.CTTS = &ctts
case TypeSTSC:
var stsc SampleToChunkBox
stsc.Decode(d.reader)
lastTrack.SampleTable.STSC = &stsc
case TypeSTSZ:
var stsz SampleSizeBox
stsz.Decode(d.reader)
lastTrack.SampleTable.STSZ = &stsz
case TypeSTCO:
var stco ChunkOffsetBox
stco.Decode(d.reader)
lastTrack.SampleTable.STCO = &stco
case TypeCO64:
var co64 ChunkLargeOffsetBox
co64.Decode(d.reader)
lastTrack.SampleTable.STCO = (*ChunkOffsetBox)(&co64)
case TypeSTSS:
var stss SyncSampleBox
stss.Decode(d.reader)
lastTrack.SampleTable.STSS = &stss
case TypeENCV:
if contentSize, err = decodeVisualSampleEntry(); err != nil {
return
}
case TypeFRMA:
buf := make([]byte, basebox.Size-BasicBoxLen)
if _, err = io.ReadFull(d.reader, buf); err != nil {
return
}
switch [4]byte(buf) {
case TypeAVC1:
lastTrack.Cid = MP4_CODEC_H264
return
case TypeMP4A:
lastTrack.Cid = MP4_CODEC_AAC
return
}
case TypeTENC:
buf := make([]byte, basebox.Size-BasicBoxLen)
if _, err = io.ReadFull(d.reader, buf); err != nil {
return
}
n := 0
versionAndFlags := binary.BigEndian.Uint32(buf[n:])
n += 5
version := byte(versionAndFlags >> 24)
track := lastTrack
if version != 0 {
infoByte := buf[n]
track.defaultCryptByteBlock = infoByte >> 4
track.defaultSkipByteBlock = infoByte & 0x0f
}
n += 1
track.defaultIsProtected = buf[n]
n += 1
track.defaultPerSampleIVSize = buf[n]
n += 1
copy(track.defaultKID[:], buf[n:n+16])
n += 16
if track.defaultIsProtected == 1 && track.defaultPerSampleIVSize == 0 {
defaultConstantIVSize := int(buf[n])
n += 1
track.defaultConstantIV = make([]byte, defaultConstantIVSize)
copy(track.defaultConstantIV, buf[n:n+defaultConstantIVSize])
}
case TypeAVC1:
lastTrack.Cid = MP4_CODEC_H264
if contentSize, err = decodeVisualSampleEntry(); err != nil {
return
}
hasChild = true
case TypeHVC1, TypeHEV1:
lastTrack.Cid = MP4_CODEC_H265
if contentSize, err = decodeVisualSampleEntry(); err != nil {
return
}
hasChild = true
case TypeENCA:
if contentSize, err = decodeAudioSampleEntry(); err != nil {
return
}
hasChild = true
case TypeMP4A:
lastTrack.Cid = MP4_CODEC_AAC
if contentSize, err = decodeAudioSampleEntry(); err != nil {
return
}
hasChild = true
case TypeULAW:
lastTrack.Cid = MP4_CODEC_G711U
if contentSize, err = decodeAudioSampleEntry(); err != nil {
return
}
hasChild = true
case TypeALAW:
lastTrack.Cid = MP4_CODEC_G711A
if contentSize, err = decodeAudioSampleEntry(); err != nil {
return
}
hasChild = true
case TypeOPUS:
lastTrack.Cid = MP4_CODEC_OPUS
if contentSize, err = decodeAudioSampleEntry(); err != nil {
return
}
hasChild = true
case TypeAVCC:
lastTrack.ExtraData = make([]byte, basebox.Size-BasicBoxLen)
if _, err = io.ReadFull(d.reader, lastTrack.ExtraData); err != nil {
return
}
case TypeHVCC:
lastTrack.ExtraData = make([]byte, basebox.Size-BasicBoxLen)
if _, err = io.ReadFull(d.reader, lastTrack.ExtraData); err != nil {
return
}
case TypeESDS:
var fullbox FullBox
fullbox.Decode(d.reader)
esds := make([]byte, basebox.Size-FullBoxLen)
if _, err = io.ReadFull(d.reader, esds); err != nil {
return
}
//TODO: check cid changed
lastTrack.Cid, lastTrack.ExtraData = DecodeESDescriptor(esds)
case TypeEDTS:
hasChild = true
case TypeELST:
var elst EditListBox
elst.Decode(d.reader)
lastTrack.ELST = &elst
case TypeMVEX:
d.IsFragment = true
case TypeMOOF:
d.moofOffset = basebox.Offset
d.dataOffset = uint32(basebox.Size) + 8
hasChild = true
case TypeMFHD:
var mfhd MovieFragmentHeaderBox
mfhd.Decode(d.reader)
case TypeTRAF:
hasChild = true
case TypeTFHD:
var tfhd TrackFragmentHeaderBox
tfhd.Decode(d.reader, uint32(basebox.Size), uint64(d.moofOffset))
for i := 0; i < len(d.Tracks); i++ {
if d.Tracks[i].TrackId != tfhd.Track_ID {
continue
for _, trak := range box.Tracks {
track := &Track{}
track.TrackId = trak.TKHD.TrackID
track.Duration = uint32(trak.TKHD.Duration)
track.Timescale = trak.MDIA.MDHD.Timescale
track.Samplelist = trak.ParseSamples()
track.ELST = trak.EDTS.Elst
if len(trak.MDIA.MINF.STBL.STSD.Entries) > 0 {
entryBox := trak.MDIA.MINF.STBL.STSD.Entries[0]
switch entry := entryBox.(type) {
case *AudioSampleEntry:
switch entry.Type() {
case TypeMP4A:
track.Cid = MP4_CODEC_AAC
case TypeALAW:
track.Cid = MP4_CODEC_G711A
case TypeULAW:
track.Cid = MP4_CODEC_G711U
case TypeOPUS:
track.Cid = MP4_CODEC_OPUS
}
switch extra := entry.ExtraData.(type) {
case *ESDSBox:
track.Cid, track.ExtraData = DecodeESDescriptor(extra.Data)
}
case *VisualSampleEntry:
track.ExtraData = entry.ExtraData.(*DataBox).Data
switch entry.Type() {
case TypeAVC1:
track.Cid = MP4_CODEC_H264
case TypeHVCC:
track.Cid = MP4_CODEC_H265
}
}
}
d.currentTrack = d.Tracks[i]
d.Tracks[i].defaultDuration = tfhd.DefaultSampleDuration
d.Tracks[i].defaultSize = tfhd.DefaultSampleSize
d.Tracks[i].baseDataOffset = tfhd.BaseDataOffset
d.Tracks = append(d.Tracks, track)
}
case TypeTFDT:
var tfdt TrackFragmentBaseMediaDecodeTimeBox
tfdt.Decode(d.reader, uint32(basebox.Size))
d.currentTrack.StartDts = tfdt.BaseMediaDecodeTime
case TypeTRUN:
var trun TrackRunBox
trun.Decode(d.reader, uint32(basebox.Size), int32(d.dataOffset))
d.decodeTRUN(&trun)
case TypeSENC:
var senc SencBox
senc.Decode(d.reader, uint32(basebox.Size), lastTrack.defaultPerSampleIVSize)
d.currentTrack.subSamples = append(d.currentTrack.subSamples, senc.EntryList...)
case TypeSAIZ:
var saiz SaizBox
saiz.Decode(d.reader, uint32(basebox.Size))
d.currentTrack.lastSaiz = &saiz
case TypeSAIO:
var saio SaioBox
saio.Decode(d.reader, uint32(basebox.Size))
if err = d.decodeSaioBox(&saio); err != nil {
return
}
case TypeUUID:
var uuid [16]byte
if _, err = io.ReadFull(d.reader, uuid[:]); err != nil {
return
}
// TODO
// _, err = d.reader.Seek(int64(basebox.Size)-BasicBoxLen-16, io.SeekCurrent)
case TypeSGPD:
if err = d.decodeSgpdBox(uint32(basebox.Size)); err != nil {
return
}
case TypeWAVE:
if _, err = io.ReadFull(d.reader, make([]byte, 24)); err != nil {
return
}
default:
if basebox.Size != BasicBoxLen {
_, err = d.reader.Seek(int64(basebox.Size)-BasicBoxLen, io.SeekCurrent)
if err != nil {
return
}
d.moov = box
case *MovieFragmentBox:
for _, traf := range box.TRAFs {
track := d.Tracks[traf.TFHD.TrackID-1]
track.defaultSize = traf.TFHD.DefaultSampleSize
track.defaultDuration = traf.TFHD.DefaultSampleDuration
}
}
if hasChild {
nextChildBoxOffset += int64(contentSize)
offset = nextChildBoxOffset
}
// n, err := d.reader.Seek(0, io.SeekCurrent)
// if err != nil {
// panic(err)
// }
// if n != offset {
// panic("seek error")
// }
//}
}
if err != io.EOF {
return err
}
if !d.IsFragment {
d.buildSampleList()
}
d.ReadSampleIdx = make([]uint32, len(d.Tracks))
for _, track := range d.Tracks {
if len(track.Samplelist) > 0 {
track.StartDts = uint64(track.Samplelist[0].DTS) * 1000 / uint64(track.Timescale)
track.EndDts = uint64(track.Samplelist[len(track.Samplelist)-1].DTS) * 1000 / uint64(track.Timescale)
}
}
// for _, track := range d.Tracks {
// if len(track.Samplelist) > 0 {
// track.StartDts = uint64(track.Samplelist[0].DTS) * 1000 / uint64(track.Timescale)
// track.EndDts = uint64(track.Samplelist[len(track.Samplelist)-1].DTS) * 1000 / uint64(track.Timescale)
// }
// }
return nil
}
@@ -461,242 +205,164 @@ func (d *Demuxer) SeekTime(dts uint64) (sample *Sample, err error) {
return
}
func (d *Demuxer) buildSampleList() {
for _, track := range d.Tracks {
stbl := &track.SampleTable
l := len(*stbl.STCO)
chunks := make([]movchunk, l)
iterator := 0
for i := 0; i < l; i++ {
chunks[i].chunknum = uint32(i + 1)
chunks[i].chunkoffset = (*stbl.STCO)[i]
for iterator+1 < len(*stbl.STSC) && (*stbl.STSC)[iterator+1].FirstChunk <= chunks[i].chunknum {
iterator++
}
chunks[i].samplenum = (*stbl.STSC)[iterator].SamplesPerChunk
}
track.Samplelist = make([]Sample, stbl.STSZ.SampleCount)
for i := range track.Samplelist {
if stbl.STSZ.SampleSize == 0 {
track.Samplelist[i].Size = int(stbl.STSZ.EntrySizelist[i])
} else {
track.Samplelist[i].Size = int(stbl.STSZ.SampleSize)
}
}
iterator = 0
for i := range chunks {
for j := 0; j < int(chunks[i].samplenum); j++ {
if iterator >= len(track.Samplelist) {
break
}
if j == 0 {
track.Samplelist[iterator].Offset = int64(chunks[i].chunkoffset)
} else {
track.Samplelist[iterator].Offset = track.Samplelist[iterator-1].Offset + int64(track.Samplelist[iterator-1].Size)
}
iterator++
}
}
iterator = 0
track.Samplelist[iterator].DTS = 0
if track.ELST != nil {
for _, entry := range track.ELST.Entrys {
if entry.MediaTime == -1 {
track.Samplelist[iterator].DTS = (entry.SegmentDuration)
}
}
}
iterator++
for _, entry := range *stbl.STTS {
for j := 0; j < int(entry.SampleCount); j++ {
if iterator == len(track.Samplelist) {
break
}
track.Samplelist[iterator].DTS = uint64(entry.SampleDelta) + track.Samplelist[iterator-1].DTS
iterator++
}
}
// func (d *Demuxer) decodeTRUN(trun *TrackRunBox) {
// dataOffset := trun.Dataoffset
// nextDts := d.currentTrack.StartDts
// delta := 0
// var cts int64 = 0
// for _, entry := range trun.EntryList {
// sample := Sample{}
// sample.Offset = int64(dataOffset) + int64(d.currentTrack.baseDataOffset)
// sample.DTS = (nextDts)
// if entry.SampleSize == 0 {
// dataOffset += int32(d.currentTrack.defaultSize)
// sample.Size = int(d.currentTrack.defaultSize)
// } else {
// dataOffset += int32(entry.SampleSize)
// sample.Size = int(entry.SampleSize)
// }
// no ctts table, so pts == dts
if stbl.CTTS == nil || len(*stbl.CTTS) == 0 {
for i := range track.Samplelist {
track.Samplelist[i].PTS = track.Samplelist[i].DTS
}
} else {
iterator = 0
for i := range *stbl.CTTS {
for j := 0; j < int((*stbl.CTTS)[i].SampleCount); j++ {
track.Samplelist[iterator].PTS = (track.Samplelist[iterator].DTS) + uint64((*stbl.CTTS)[i].SampleOffset)
iterator++
}
}
}
if stbl.STSS != nil {
for _, keyIndex := range *stbl.STSS {
track.Samplelist[keyIndex-1].KeyFrame = true
}
}
}
}
// if entry.SampleDuration == 0 {
// delta = int(d.currentTrack.defaultDuration)
// } else {
// delta = int(entry.SampleDuration)
// }
// cts = int64(entry.SampleCompositionTimeOffset)
// sample.PTS = uint64(int64(sample.DTS) + cts)
// nextDts += uint64(delta)
// d.currentTrack.Samplelist = append(d.currentTrack.Samplelist, sample)
// }
// d.dataOffset = uint32(dataOffset)
// }
func (d *Demuxer) decodeTRUN(trun *TrackRunBox) {
dataOffset := trun.Dataoffset
nextDts := d.currentTrack.StartDts
delta := 0
var cts int64 = 0
for _, entry := range trun.EntryList {
sample := Sample{}
sample.Offset = int64(dataOffset) + int64(d.currentTrack.baseDataOffset)
sample.DTS = (nextDts)
if entry.SampleSize == 0 {
dataOffset += int32(d.currentTrack.defaultSize)
sample.Size = int(d.currentTrack.defaultSize)
} else {
dataOffset += int32(entry.SampleSize)
sample.Size = int(entry.SampleSize)
}
// func (d *Demuxer) decodeSaioBox(saio *SaioBox) (err error) {
// if len(saio.Offset) > 0 && len(d.currentTrack.subSamples) == 0 {
// var currentOffset int64
// currentOffset, err = d.reader.Seek(0, io.SeekCurrent)
// if err != nil {
// return err
// }
// d.reader.Seek(d.moofOffset+saio.Offset[0], io.SeekStart)
// saiz := d.currentTrack.lastSaiz
// for i := uint32(0); i < saiz.SampleCount; i++ {
// sampleSize := saiz.DefaultSampleInfoSize
// if saiz.DefaultSampleInfoSize == 0 {
// sampleSize = saiz.SampleInfo[i]
// }
// buf := make([]byte, sampleSize)
// d.reader.Read(buf)
// var se SencEntry
// se.IV = make([]byte, 16)
// copy(se.IV, buf[:8])
// if sampleSize == 8 {
// d.currentTrack.subSamples = append(d.currentTrack.subSamples, se)
// continue
// }
// n := 8
// sampleCount := binary.BigEndian.Uint16(buf[n:])
// n += 2
if entry.SampleDuration == 0 {
delta = int(d.currentTrack.defaultDuration)
} else {
delta = int(entry.SampleDuration)
}
cts = int64(entry.SampleCompositionTimeOffset)
sample.PTS = uint64(int64(sample.DTS) + cts)
nextDts += uint64(delta)
d.currentTrack.Samplelist = append(d.currentTrack.Samplelist, sample)
}
d.dataOffset = uint32(dataOffset)
}
// se.SubSamples = make([]SubSampleEntry, sampleCount)
// for j := 0; j < int(sampleCount); j++ {
// se.SubSamples[j].BytesOfClearData = binary.BigEndian.Uint16(buf[n:])
// n += 2
// se.SubSamples[j].BytesOfProtectedData = binary.BigEndian.Uint32(buf[n:])
// n += 4
// }
// d.currentTrack.subSamples = append(d.currentTrack.subSamples, se)
// }
// d.reader.Seek(currentOffset, io.SeekStart)
// }
// return nil
// }
func (d *Demuxer) decodeSaioBox(saio *SaioBox) (err error) {
if len(saio.Offset) > 0 && len(d.currentTrack.subSamples) == 0 {
var currentOffset int64
currentOffset, err = d.reader.Seek(0, io.SeekCurrent)
if err != nil {
return err
}
d.reader.Seek(d.moofOffset+saio.Offset[0], io.SeekStart)
saiz := d.currentTrack.lastSaiz
for i := uint32(0); i < saiz.SampleCount; i++ {
sampleSize := saiz.DefaultSampleInfoSize
if saiz.DefaultSampleInfoSize == 0 {
sampleSize = saiz.SampleInfo[i]
}
buf := make([]byte, sampleSize)
d.reader.Read(buf)
var se SencEntry
se.IV = make([]byte, 16)
copy(se.IV, buf[:8])
if sampleSize == 8 {
d.currentTrack.subSamples = append(d.currentTrack.subSamples, se)
continue
}
n := 8
sampleCount := binary.BigEndian.Uint16(buf[n:])
n += 2
// func (d *Demuxer) decodeSgpdBox(size uint32) (err error) {
// buf := make([]byte, size-BasicBoxLen)
// if _, err = io.ReadFull(d.reader, buf); err != nil {
// return
// }
// n := 0
// versionAndFlags := binary.BigEndian.Uint32(buf[n:])
// n += 4
// version := byte(versionAndFlags >> 24)
se.SubSamples = make([]SubSampleEntry, sampleCount)
for j := 0; j < int(sampleCount); j++ {
se.SubSamples[j].BytesOfClearData = binary.BigEndian.Uint16(buf[n:])
n += 2
se.SubSamples[j].BytesOfProtectedData = binary.BigEndian.Uint32(buf[n:])
n += 4
}
d.currentTrack.subSamples = append(d.currentTrack.subSamples, se)
}
d.reader.Seek(currentOffset, io.SeekStart)
}
return nil
}
// b := &SgpdBox{
// Version: version,
// Flags: versionAndFlags & 0x00ffffff,
// }
// b.GroupingType = string(buf[n : n+4])
// n += 4
func (d *Demuxer) decodeSgpdBox(size uint32) (err error) {
buf := make([]byte, size-BasicBoxLen)
if _, err = io.ReadFull(d.reader, buf); err != nil {
return
}
n := 0
versionAndFlags := binary.BigEndian.Uint32(buf[n:])
n += 4
version := byte(versionAndFlags >> 24)
// if b.Version >= 1 {
// b.DefaultLength = binary.BigEndian.Uint32(buf[n:])
// n += 4
// }
// if b.Version >= 2 {
// b.DefaultGroupDescriptionIndex = binary.BigEndian.Uint32(buf[n:])
// n += 4
// }
// entryCount := int(binary.BigEndian.Uint32(buf[n:]))
// n += 4
b := &SgpdBox{
Version: version,
Flags: versionAndFlags & 0x00ffffff,
}
b.GroupingType = string(buf[n : n+4])
n += 4
// track := d.Tracks[len(d.Tracks)-1]
// for i := 0; i < entryCount; i++ {
// var descriptionLength = b.DefaultLength
// if b.Version >= 1 && b.DefaultLength == 0 {
// descriptionLength = binary.BigEndian.Uint32(buf[n:])
// n += 4
// b.DescriptionLengths = append(b.DescriptionLengths, descriptionLength)
// }
// var (
// sgEntry interface{}
// offset int
// )
// sgEntry, offset, err = DecodeSampleGroupEntry(b.GroupingType, descriptionLength, buf[n:])
// n += offset
// if err != nil {
// return err
// }
// if sgEntry == nil {
// continue
// }
// if seig, ok := sgEntry.(*SeigSampleGroupEntry); ok {
// track.lastSeig = seig
// }
// b.SampleGroupEntries = append(b.SampleGroupEntries, sgEntry)
// }
if b.Version >= 1 {
b.DefaultLength = binary.BigEndian.Uint32(buf[n:])
n += 4
}
if b.Version >= 2 {
b.DefaultGroupDescriptionIndex = binary.BigEndian.Uint32(buf[n:])
n += 4
}
entryCount := int(binary.BigEndian.Uint32(buf[n:]))
n += 4
// return nil
// }
track := d.Tracks[len(d.Tracks)-1]
for i := 0; i < entryCount; i++ {
var descriptionLength = b.DefaultLength
if b.Version >= 1 && b.DefaultLength == 0 {
descriptionLength = binary.BigEndian.Uint32(buf[n:])
n += 4
b.DescriptionLengths = append(b.DescriptionLengths, descriptionLength)
}
var (
sgEntry interface{}
offset int
)
sgEntry, offset, err = DecodeSampleGroupEntry(b.GroupingType, descriptionLength, buf[n:])
n += offset
if err != nil {
return err
}
if sgEntry == nil {
continue
}
if seig, ok := sgEntry.(*SeigSampleGroupEntry); ok {
track.lastSeig = seig
}
b.SampleGroupEntries = append(b.SampleGroupEntries, sgEntry)
}
return nil
}
func (d *Demuxer) readSubSample(idx uint32, track *Track) (subSample *SubSample) {
if int(idx) < len(track.subSamples) {
subSample = new(SubSample)
subSample.Number = idx
if len(track.subSamples[idx].IV) > 0 {
copy(subSample.IV[:], track.subSamples[idx].IV)
} else {
copy(subSample.IV[:], track.defaultConstantIV)
}
if track.lastSeig != nil {
copy(subSample.KID[:], track.lastSeig.KID[:])
subSample.CryptByteBlock = track.lastSeig.CryptByteBlock
subSample.SkipByteBlock = track.lastSeig.SkipByteBlock
} else {
copy(subSample.KID[:], track.defaultKID[:])
subSample.CryptByteBlock = track.defaultCryptByteBlock
subSample.SkipByteBlock = track.defaultSkipByteBlock
}
subSample.PsshBoxes = append(subSample.PsshBoxes, d.pssh...)
if len(track.subSamples[idx].SubSamples) > 0 {
subSample.Patterns = make([]SubSamplePattern, len(track.subSamples[idx].SubSamples))
for ei, e := range track.subSamples[idx].SubSamples {
subSample.Patterns[ei].BytesClear = e.BytesOfClearData
subSample.Patterns[ei].BytesProtected = e.BytesOfProtectedData
}
}
return subSample
}
return nil
}
// func (d *Demuxer) readSubSample(idx uint32, track *Track) (subSample *SubSample) {
// if int(idx) < len(track.subSamples) {
// subSample = new(SubSample)
// subSample.Number = idx
// if len(track.subSamples[idx].IV) > 0 {
// copy(subSample.IV[:], track.subSamples[idx].IV)
// } else {
// copy(subSample.IV[:], track.defaultConstantIV)
// }
// if track.lastSeig != nil {
// copy(subSample.KID[:], track.lastSeig.KID[:])
// subSample.CryptByteBlock = track.lastSeig.CryptByteBlock
// subSample.SkipByteBlock = track.lastSeig.SkipByteBlock
// } else {
// copy(subSample.KID[:], track.defaultKID[:])
// subSample.CryptByteBlock = track.defaultCryptByteBlock
// subSample.SkipByteBlock = track.defaultSkipByteBlock
// }
// subSample.PsshBoxes = append(subSample.PsshBoxes, d.pssh...)
// if len(track.subSamples[idx].SubSamples) > 0 {
// subSample.Patterns = make([]SubSamplePattern, len(track.subSamples[idx].SubSamples))
// for ei, e := range track.subSamples[idx].SubSamples {
// subSample.Patterns[ei].BytesClear = e.BytesOfClearData
// subSample.Patterns[ei].BytesProtected = e.BytesOfProtectedData
// }
// }
// return subSample
// }
// return nil
// }
func (d *Demuxer) ReadSample(yield func(*Track, Sample) bool) {
for {
@@ -714,8 +380,8 @@ func (d *Demuxer) ReadSample(yield func(*Track, Sample) bool) {
whichTrack = track
whichTracki = i
} else {
dts1 := minTsSample.DTS * uint64(d.Timescale) / uint64(whichTrack.Timescale)
dts2 := track.Samplelist[idx].DTS * uint64(d.Timescale) / uint64(track.Timescale)
dts1 := minTsSample.DTS * uint64(d.moov.MVHD.Timescale) / uint64(whichTrack.Timescale)
dts2 := track.Samplelist[idx].DTS * uint64(d.moov.MVHD.Timescale) / uint64(track.Timescale)
if dts1 > dts2 {
minTsSample = track.Samplelist[idx]
whichTrack = track

View File

@@ -3,9 +3,11 @@ package mp4
import (
"encoding/binary"
"io"
"net"
"os"
"m7s.live/v5/pkg"
"m7s.live/v5/plugin/mp4/pkg/box"
. "m7s.live/v5/plugin/mp4/pkg/box"
)
@@ -26,7 +28,7 @@ type (
Tracks map[uint32]*Track
Flag
fragDuration uint32
moov *BasicBox
moov IBox
mdatOffset uint64
mdatSize uint64
}
@@ -55,49 +57,38 @@ func NewMuxer(flag Flag) *Muxer {
}
func (m *Muxer) WriteInitSegment(w io.Writer) (err error) {
var n int
var ftypBox []byte
var ftypBox *FileTypeBox
if m.isFragment() {
// 对于 FMP4,使用 iso5 作为主品牌,兼容 iso5, iso6, mp41
ftypBox = MakeFtypBox(TypeISO5, 0x200, TypeISO5, TypeISO6, TypeMP41)
ftypBox = CreateFTYPBox(TypeISO5, 0x200, TypeISO5, TypeISO6, TypeMP41)
} else {
// 对于普通 MP4,使用 isom 作为主品牌
ftypBox = MakeFtypBox(TypeISOM, 0x200, TypeISOM, TypeISO2, TypeAVC1, TypeMP41)
ftypBox = CreateFTYPBox(TypeISOM, 0x200, TypeISOM, TypeISO2, TypeAVC1, TypeMP41)
}
n, err = w.Write(ftypBox)
m.CurrentOffset, err = box.WriteTo(w, ftypBox)
if err != nil {
return
}
m.CurrentOffset = int64(n)
if !m.isFragment() {
n, err = w.Write((new(FreeBox)).Encode())
var n int64
freeBox := CreateFreeBox(nil)
n, err = box.WriteTo(w, freeBox)
if err != nil {
return
}
m.CurrentOffset += int64(n)
err = m.WriteEmptyMdat(w)
m.CurrentOffset += n
mdat := CreateDataBox(TypeMDAT, nil)
n, err = box.WriteTo(w, mdat)
if err != nil {
return
}
m.mdatOffset = uint64(m.CurrentOffset + 8)
m.mdatSize = 0
m.CurrentOffset += n
}
return
}
func (m *Muxer) WriteEmptyMdat(w io.Writer) (err error) {
// Write mdat box header with initial size
mdat := MediaDataBox(0)
mdatlen, mdatBox := mdat.Encode()
m.mdatOffset = uint64(m.CurrentOffset + 8)
m.mdatSize = 0
var n int
n, err = w.Write(mdatBox[0:mdatlen])
if err != nil {
return
}
m.CurrentOffset += int64(n)
return
}
func (m *Muxer) AddTrack(cid MP4_CODEC_TYPE) *Track {
track := &Track{
Cid: cid,
@@ -144,11 +135,12 @@ func (m *Muxer) WriteSample(w io.Writer, t *Track, sample Sample) (err error) {
func (m *Muxer) reWriteMdatSize(w io.WriteSeeker) (err error) {
m.mdatSize = uint64(m.CurrentOffset) - (m.mdatOffset)
if m.mdatSize+BasicBoxLen > 0xFFFFFFFF {
_, mdatBox := MediaDataBox(m.mdatSize).Encode()
mdat := CreateBaseBox(TypeMDAT, m.mdatSize+BasicBoxLen)
// 覆盖FreeBox
if _, err = w.Seek(int64(m.mdatOffset-16), io.SeekStart); err != nil {
return
}
if _, err = w.Write(mdatBox); err != nil {
if _, err = box.WriteTo(w, mdat); err != nil {
return
}
if _, err = w.Seek(m.CurrentOffset, io.SeekStart); err != nil {
@@ -184,7 +176,7 @@ func (m *Muxer) ReWriteWithMoov(f io.WriteSeeker, r io.Reader) (err error) {
}
for _, track := range m.Tracks {
for i := range len(track.Samplelist) {
track.Samplelist[i].Offset += int64(m.moov.Size)
track.Samplelist[i].Offset += int64(m.moov.Size())
}
}
err = m.WriteMoov(f)
@@ -195,33 +187,28 @@ func (m *Muxer) ReWriteWithMoov(f io.WriteSeeker, r io.Reader) (err error) {
return
}
func (m *Muxer) makeMvex() []byte {
mvex := BasicBox{Type: TypeMVEX}
trexs := make([]byte, 0, 64)
func (m *Muxer) makeMvex() *box.MovieExtendsBox {
trexs := make([]*box.TrackExtendsBox, 0, m.nextTrackId-1)
for i := uint32(1); i < m.nextTrackId; i++ {
if track := m.Tracks[i]; track != nil {
trex := NewTrackExtendsBox(track.TrackId)
trex := box.CreateTrackExtendsBox(track.TrackId)
trex.DefaultSampleDescriptionIndex = 1
trex.DefaultSampleDuration = 0
trex.DefaultSampleSize = 0
if track.Cid.IsVideo() {
trex.DefaultSampleFlags = 0x00010000 // NonSyncSampleFlags in mp4ff
} else {
trex.DefaultSampleFlags = 0x02000000 // SyncSampleFlags in mp4ff
}
_, boxData := trex.Encode()
trexs = append(trexs, boxData...)
trexs = append(trexs, trex)
}
}
mvex.Size = 8 + uint64(len(trexs))
offset, mvexBox := mvex.Encode()
copy(mvexBox[offset:], trexs)
return mvexBox
return box.CreateMovieExtendsBox(trexs)
}
func (m *Muxer) makeTrak(track *Track) []byte {
edts := []byte{}
func (m *Muxer) makeTrak(track *Track) *ContainerBox {
var edts *ContainerBox
if m.isDash() || m.isFragment() {
// track.makeEmptyStblTable()
} else {
if len(track.Samplelist) > 0 {
@@ -229,73 +216,45 @@ func (m *Muxer) makeTrak(track *Track) []byte {
edts = track.makeEdtsBox()
}
}
tkhd := track.makeTkhdBox()
mdia := track.makeMdiaBox()
trak := BasicBox{Type: TypeTRAK}
trak.Size = 8 + uint64(len(tkhd)+len(edts)+len(mdia))
offset, trakBox := trak.Encode()
copy(trakBox[offset:], tkhd)
offset += len(tkhd)
copy(trakBox[offset:], edts)
offset += len(edts)
copy(trakBox[offset:], mdia)
return trakBox
return CreateContainerBox(TypeTRAK, tkhd, mdia, edts)
}
func (m *Muxer) GetMoovSize() int {
moovsize := FullBoxLen + 96
moovsize := uint64(FullBoxLen + 96)
if m.isDash() || m.isFragment() {
moovsize += 64
}
traks := make([][]byte, len(m.Tracks))
for i := uint32(1); i < m.nextTrackId; i++ {
traks[i-1] = m.makeTrak(m.Tracks[i])
moovsize += len(traks[i-1])
for _, track := range m.Tracks {
moovsize += uint64(m.makeTrak(track).Size())
}
return int(8 + uint64(moovsize))
return int(8 + moovsize)
}
func (m *Muxer) WriteMoov(w io.Writer) (err error) {
var mvhd []byte
var mvex []byte
if m.isDash() || m.isFragment() {
mvhd = MakeMvhdBox(m.nextTrackId, 0)
mvex = m.makeMvex()
} else {
maxdurtaion := uint32(0)
for _, track := range m.Tracks {
if maxdurtaion < track.Duration {
maxdurtaion = track.Duration
}
var mvhd *box.MovieHeaderBox
var mvex *box.MovieExtendsBox
var children []IBox
maxdurtaion := uint32(0)
for _, track := range m.Tracks {
children = append(children, m.makeTrak(track))
if maxdurtaion < track.Duration {
maxdurtaion = track.Duration
}
mvhd = MakeMvhdBox(m.nextTrackId, maxdurtaion)
}
moovsize := len(mvhd) + len(mvex)
traks := make([][]byte, len(m.Tracks))
for i := uint32(1); i < m.nextTrackId; i++ {
traks[i-1] = m.makeTrak(m.Tracks[i])
moovsize += len(traks[i-1])
if m.isDash() || m.isFragment() {
mvhd = box.CreateMovieHeaderBox(m.nextTrackId, 0)
mvex = m.makeMvex()
children = append(children, mvex)
} else {
mvhd = box.CreateMovieHeaderBox(m.nextTrackId, maxdurtaion)
}
moov := BasicBox{Type: TypeMOOV}
moov.Size = 8 + uint64(moovsize)
offset, moovBox := moov.Encode()
copy(moovBox[offset:], mvhd)
offset += len(mvhd)
for _, trak := range traks {
copy(moovBox[offset:], trak)
offset += len(trak)
}
if mvex != nil {
copy(moovBox[offset:], mvex)
}
// Write moov box
_, err = w.Write(moovBox)
m.moov = &moov
m.CurrentOffset += int64(moov.Size)
children = append(children, mvhd)
m.moov = box.CreateContainerBox(TypeMOOV, children...)
var n int64
n, err = box.WriteTo(w, m.moov)
m.CurrentOffset += n
return
}
@@ -305,37 +264,27 @@ func (m *Muxer) WriteTrailer(file *os.File) (err error) {
if err = m.flushFragment(file); err != nil {
return err
}
var mfraChildren []box.IBox
var mfraSize uint32 = 0
// Write mfra box
mfraSize := 0
tfras := make([][]byte, len(m.Tracks))
tfras := make([]*box.TrackFragmentRandomAccessBox, len(m.Tracks))
for i := uint32(1); i < m.nextTrackId; i++ {
if track := m.Tracks[i]; track != nil && len(track.fragments) > 0 {
tfras[i-1] = track.makeTfraBox()
mfraSize += len(tfras[i-1])
mfraChildren = append(mfraChildren, tfras[i-1])
mfraSize += uint32(tfras[i-1].Size())
}
}
// Only write mfra if we have fragments
if mfraSize > 0 {
mfro := MakeMfroBox(uint32(mfraSize) + 16)
mfraSize += len(mfro)
mfra := BasicBox{Type: TypeMFRA}
mfra.Size = 8 + uint64(mfraSize)
offset, mfraBox := mfra.Encode()
for _, tfra := range tfras {
if tfra == nil {
continue
}
copy(mfraBox[offset:], tfra)
offset += len(tfra)
}
copy(mfraBox[offset:], mfro)
if _, err = file.Write(mfraBox); err != nil {
mfraChildren = append(mfraChildren, box.CreateMfroBox(uint32(mfraSize)+16))
mfra := box.CreateContainerBox(TypeMFRA, mfraChildren...)
_, err = box.WriteTo(file, mfra)
if err != nil {
return err
}
}
// Clean up any remaining buffers
for i := uint32(1); i < m.nextTrackId; i++ {
if track := m.Tracks[i]; track != nil && track.writer != nil {
@@ -383,59 +332,32 @@ func (m *Muxer) flushFragment(w io.Writer) (err error) {
}
// Write moof box
mfhd := MakeMfhdBox(m.nextFragmentId)
trafs := make([][]byte, len(m.Tracks))
moofSize := len(mfhd)
trunOffsets := make([]int, len(m.Tracks)) // track index -> trun data_offset position in moof box
var boxOffset int = 8 + len(mfhd) // 8 for moof header
mfhdBox := box.CreateMovieFragmentHeaderBox(m.nextFragmentId)
trafs := make([]*box.TrackFragmentBox, len(m.Tracks))
moofChildren := make([]box.IBox, 0, len(m.Tracks)+1)
moofChildren = append(moofChildren, mfhdBox)
for i := uint32(1); i < m.nextTrackId; i++ {
if len(m.Tracks[i].Samplelist) == 0 {
continue
}
track := m.Tracks[i]
// 传递 moof 偏移和 mdat 大小
traf := track.makeTraf(&trunOffsets[int(i-1)]) // +8 for moof box header
traf := track.makeTraf() // +8 for moof box header
// Record trun data_offset position: current offset + 16 (after trun header)
trafs[i-1] = traf
trunOffsets[int(i-1)] += boxOffset
boxOffset += len(traf)
moofSize += len(traf)
moofChildren = append(moofChildren, traf)
}
// Write moof box
moof := BasicBox{Type: TypeMOOF}
moof.Size = uint64(moofSize + 8) // Add 8 for moof box header
offset, moofBox := moof.Encode()
copy(moofBox[offset:], mfhd)
offset += len(mfhd)
for i, traf := range trafs {
if traf == nil {
continue
}
copy(moofBox[offset:], traf)
// Update trun data_offset
binary.BigEndian.PutUint32(moofBox[trunOffsets[i]:trunOffsets[i]+4], uint32(moof.Size)+8) // +8 for mdat header
offset += len(traf)
}
if _, err = w.Write(moofBox); err != nil {
return err
}
// Write mdat box
mdat := BasicBox{Type: TypeMDAT}
mdat.Size = mdatSize
offset, mdatBox := mdat.Encode()
if _, err = w.Write(mdatBox[:offset]); err != nil {
return err
}
moof := CreateContainerBox(TypeMOOF, moofChildren...)
sampleData := make(net.Buffers, len(m.Tracks))
// Write sample data
var sampleOffset int64 = 0
for i := uint32(1); i < m.nextTrackId; i++ {
if len(m.Tracks[i].Samplelist) == 0 {
continue
}
track := m.Tracks[i]
ws := track.writer.(*Fmp4WriterSeeker)
@@ -445,9 +367,7 @@ func (m *Muxer) flushFragment(w io.Writer) (err error) {
sampleOffset += int64(track.Samplelist[j].Size)
}
if _, err = w.Write(ws.Buffer); err != nil {
return err
}
sampleData[i-1] = ws.Buffer
// Record fragment info
if len(track.Samplelist) > 0 {
@@ -472,7 +392,30 @@ func (m *Muxer) flushFragment(w io.Writer) (err error) {
track.Samplelist = track.Samplelist[:0]
track.Duration = 0
}
m.CurrentOffset += int64(moof.Size) + int64(mdatSize)
// Write mdat box
mdat := CreateBaseBox(TypeMDAT, uint64(sampleOffset)+BasicBoxLen)
for _, traf := range trafs {
traf.TRUN.DataOffset = int32(moof.Size()) + int32(mdat.HeaderSize())
}
var n int64
n, err = box.WriteTo(w, moof)
if err != nil {
return err
}
m.CurrentOffset += n
n, err = mdat.HeaderWriteTo(w)
if err != nil {
return err
}
m.CurrentOffset += n
n, err = sampleData.WriteTo(w)
if err != nil {
return err
}
m.CurrentOffset += n
m.nextFragmentId++
return nil
}

View File

@@ -0,0 +1,359 @@
package mp4
import (
"encoding/binary"
"fmt"
"io"
"os"
"os/exec"
"testing"
"m7s.live/v5/plugin/mp4/pkg/box"
)
type (
FLVHeader struct {
Signature [3]byte
Version uint8
Flags uint8
DataOffset uint32
}
FLVTag struct {
TagType uint8
DataSize uint32
Timestamp uint32
StreamID uint32
Data []byte
}
)
// validateAndFixAVCC 验证并修复 AVCC 格式的 NALU
func validateAndFixAVCC(data []byte) ([]byte, error) {
if len(data) < 4 {
return nil, fmt.Errorf("data too short for AVCC")
}
var pos int
var output []byte
for pos < len(data) {
if pos+4 > len(data) {
return nil, fmt.Errorf("incomplete NALU length at position %d", pos)
}
// 读取 NALU 长度4字节大端序
naluLen := binary.BigEndian.Uint32(data[pos : pos+4])
// 验证 NALU 长度
if naluLen == 0 || pos+4+int(naluLen) > len(data) {
return nil, fmt.Errorf("invalid NALU length %d at position %d", naluLen, pos)
}
// 验证 NALU 类型
naluType := data[pos+4] & 0x1F
if naluType == 0 || naluType > 12 {
return nil, fmt.Errorf("invalid NALU type %d at position %d", naluType, pos)
}
// 复制长度前缀和 NALU 数据
output = append(output, data[pos:pos+4+int(naluLen)]...)
pos += 4 + int(naluLen)
}
return output, nil
}
func readFLVHeader(r io.Reader) (*FLVHeader, error) {
header := &FLVHeader{}
if err := binary.Read(r, binary.BigEndian, &header.Signature); err != nil {
return nil, fmt.Errorf("error reading signature: %v", err)
}
if err := binary.Read(r, binary.BigEndian, &header.Version); err != nil {
return nil, fmt.Errorf("error reading version: %v", err)
}
if err := binary.Read(r, binary.BigEndian, &header.Flags); err != nil {
return nil, fmt.Errorf("error reading flags: %v", err)
}
if err := binary.Read(r, binary.BigEndian, &header.DataOffset); err != nil {
return nil, fmt.Errorf("error reading data offset: %v", err)
}
// Validate FLV signature
if string(header.Signature[:]) != "FLV" {
return nil, fmt.Errorf("invalid FLV signature: %s", string(header.Signature[:]))
}
fmt.Printf("FLV Header: Version=%d, Flags=%d, DataOffset=%d\n", header.Version, header.Flags, header.DataOffset)
return header, nil
}
func readFLVTag(r io.Reader) (*FLVTag, error) {
tag := &FLVTag{}
// Read previous tag size (4 bytes)
var prevTagSize uint32
if err := binary.Read(r, binary.BigEndian, &prevTagSize); err != nil {
return nil, err
}
// Read tag type (1 byte)
if err := binary.Read(r, binary.BigEndian, &tag.TagType); err != nil {
return nil, err
}
// Read data size (3 bytes)
var dataSize [3]byte
if _, err := io.ReadFull(r, dataSize[:]); err != nil {
return nil, err
}
tag.DataSize = uint32(dataSize[0])<<16 | uint32(dataSize[1])<<8 | uint32(dataSize[2])
// Read timestamp (3 bytes + 1 byte extended)
var timestamp [3]byte
if _, err := io.ReadFull(r, timestamp[:]); err != nil {
return nil, err
}
var timestampExtended uint8
if err := binary.Read(r, binary.BigEndian, &timestampExtended); err != nil {
return nil, err
}
tag.Timestamp = uint32(timestamp[0])<<16 | uint32(timestamp[1])<<8 | uint32(timestamp[2]) | uint32(timestampExtended)<<24
// Read stream ID (3 bytes)
var streamID [3]byte
if _, err := io.ReadFull(r, streamID[:]); err != nil {
return nil, err
}
tag.StreamID = uint32(streamID[0])<<16 | uint32(streamID[1])<<8 | uint32(streamID[2])
// Read tag data
tag.Data = make([]byte, tag.DataSize)
if _, err := io.ReadFull(r, tag.Data); err != nil {
return nil, err
}
return tag, nil
}
func findBoxOffsets(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
type boxInfo struct {
name string
offset int64
size uint32
}
var boxes []boxInfo
// Read the entire file
data, err := io.ReadAll(file)
if err != nil {
return err
}
// Search for boxes
var i int
for i < len(data)-8 {
// Read box size (4 bytes) and type (4 bytes)
size := binary.BigEndian.Uint32(data[i : i+4])
boxType := string(data[i+4 : i+8])
// Validate box size
if size < 8 || int64(size) > int64(len(data))-int64(i) {
i++
continue
}
if boxType == "tfdt" || boxType == "mdat" || boxType == "moof" || boxType == "traf" {
boxes = append(boxes, boxInfo{
name: boxType,
offset: int64(i),
size: size,
})
// Print the entire box content for small boxes
if size <= 256 {
fmt.Printf("\nFull %s box at offset %d (0x%x):\n", boxType, i, i)
// Print box header
fmt.Printf("Header: % x\n", data[i:i+8])
// Print content in chunks of 32 bytes
for j := i + 8; j < i+int(size); j += 32 {
end := j + 32
if end > i+int(size) {
end = i + int(size)
}
fmt.Printf("Content [%d-%d]: % x\n", j-i, end-i, data[j:end])
}
}
}
// Move to the next box
i += int(size)
}
// Print box information in order of appearance
fmt.Println("\nBox layout:")
for _, box := range boxes {
fmt.Printf("%s box at offset %d (0x%x), size: %d bytes\n",
box.name, box.offset, box.offset, box.size)
// Print the first few bytes of the box content
start := box.offset + 8 // skip size and type
end := start + 32
if end > box.offset+int64(box.size) {
end = box.offset + int64(box.size)
}
fmt.Printf("%s content: % x\n", box.name, data[start:end])
// For tfdt box, also print the previous and next 8 bytes
if box.name == "tfdt" {
prevStart := box.offset - 8
if prevStart < 0 {
prevStart = 0
}
nextEnd := box.offset + int64(box.size) + 8
if nextEnd > int64(len(data)) {
nextEnd = int64(len(data))
}
fmt.Printf("Context around tfdt:\n")
fmt.Printf("Previous 8 bytes: % x\n", data[prevStart:box.offset])
fmt.Printf("Next 8 bytes: % x\n", data[box.offset+int64(box.size):nextEnd])
}
}
return nil
}
func TestFLVToFMP4(t *testing.T) {
// Open FLV file
flvFile, err := os.Open("/Users/dexter/Movies/frame_counter_4k_60fps.flv")
if err != nil {
t.Fatalf("Failed to open FLV file: %v", err)
}
defer flvFile.Close()
// Create output FMP4 file
outFile, err := os.Create("test.mp4")
if err != nil {
t.Fatalf("Failed to create output file: %v", err)
}
defer outFile.Close()
// Create FMP4 muxer
muxer := NewMuxer(FLAG_FRAGMENT)
muxer.WriteInitSegment(outFile)
// Read FLV header
header, err := readFLVHeader(flvFile)
if err != nil {
t.Fatalf("Failed to read FLV header: %v", err)
}
hasVideo := header.Flags&0x01 != 0
// Skip to the first tag
if _, err := flvFile.Seek(int64(header.DataOffset), io.SeekStart); err != nil {
t.Fatalf("Failed to seek to first tag: %v", err)
}
// Create tracks
var videoTrack *Track
if hasVideo {
videoTrack = muxer.AddTrack(box.MP4_CODEC_H264)
videoTrack.Width = 3840 // 4K resolution
videoTrack.Height = 2160
videoTrack.Timescale = 1000
}
// Variables to store codec configuration
var videoConfig []byte
var frameCount int
// Process FLV tags
// TagLoop:
for {
tag, err := readFLVTag(flvFile)
if err != nil {
if err == io.EOF {
break
}
t.Fatalf("Failed to read FLV tag: %v", err)
}
switch tag.TagType {
case 9: // Video
if !hasVideo || videoTrack == nil {
continue
}
codecID := tag.Data[0] & 0x0f
frameType := tag.Data[0] >> 4
if codecID == 7 { // AVC/H.264
if tag.Data[1] == 0 { // AVC sequence header
fmt.Println("Found AVC sequence header")
videoConfig = tag.Data[5:] // Store AVC config (skip composition time)
videoTrack.ExtraData = videoConfig
} else if len(videoConfig) > 0 { // Video data
if len(tag.Data) <= 5 {
fmt.Printf("Skipping empty video sample at timestamp %d\n", tag.Timestamp)
continue
}
// Read composition time offset (24 bits, signed)
compositionTime := int32(tag.Data[2])<<16 | int32(tag.Data[3])<<8 | int32(tag.Data[4])
// Convert 24-bit signed integer to 32-bit signed integer
if compositionTime&0x800000 != 0 {
compositionTime |= ^0xffffff
}
// 验证和修复 AVCC 格式
validData, err := validateAndFixAVCC(tag.Data[5:])
if err != nil {
fmt.Printf("Warning: Invalid AVCC data at timestamp %d: %v\n", tag.Timestamp, err)
continue
}
sample := box.Sample{
Data: validData,
DTS: uint64(tag.Timestamp),
PTS: uint64(int64(tag.Timestamp) + int64(compositionTime)),
KeyFrame: frameType == 1,
}
if err := muxer.WriteSample(outFile, videoTrack, sample); err != nil {
t.Fatalf("Failed to write video sample: %v", err)
}
frameCount++
// if frameCount >= 5 {
// fmt.Println("Wrote 5 frames, stopping")
// break TagLoop
// }
}
}
}
}
// Write trailer
if err := muxer.WriteTrailer(outFile); err != nil {
t.Fatalf("Failed to write trailer: %v", err)
}
fmt.Println("Conversion completed successfully")
// Find and analyze box positions
if err := findBoxOffsets("test.mp4"); err != nil {
t.Fatalf("Failed to analyze boxes: %v", err)
}
// Validate the generated MP4 file using MP4Box
cmd := exec.Command("MP4Box", "-info", "test.mp4")
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("MP4Box validation failed: %v\nOutput: %s", err, output)
}
fmt.Printf("MP4Box validation output:\n%s\n", output)
t.Log("Test completed successfully")
}

View File

@@ -1,7 +1,6 @@
package mp4
import (
"encoding/binary"
"fmt"
"io"
"os"
@@ -11,229 +10,7 @@ import (
"m7s.live/v5/plugin/mp4/pkg/box"
)
type (
FLVHeader struct {
Signature [3]byte
Version uint8
Flags uint8
DataOffset uint32
}
FLVTag struct {
TagType uint8
DataSize uint32
Timestamp uint32
StreamID uint32
Data []byte
}
)
// validateAndFixAVCC 验证并修复 AVCC 格式的 NALU
func validateAndFixAVCC(data []byte) ([]byte, error) {
if len(data) < 4 {
return nil, fmt.Errorf("data too short for AVCC")
}
var pos int
var output []byte
for pos < len(data) {
if pos+4 > len(data) {
return nil, fmt.Errorf("incomplete NALU length at position %d", pos)
}
// 读取 NALU 长度4字节大端序
naluLen := binary.BigEndian.Uint32(data[pos : pos+4])
// 验证 NALU 长度
if naluLen == 0 || pos+4+int(naluLen) > len(data) {
return nil, fmt.Errorf("invalid NALU length %d at position %d", naluLen, pos)
}
// 验证 NALU 类型
naluType := data[pos+4] & 0x1F
if naluType == 0 || naluType > 12 {
return nil, fmt.Errorf("invalid NALU type %d at position %d", naluType, pos)
}
// 复制长度前缀和 NALU 数据
output = append(output, data[pos:pos+4+int(naluLen)]...)
pos += 4 + int(naluLen)
}
return output, nil
}
func readFLVHeader(r io.Reader) (*FLVHeader, error) {
header := &FLVHeader{}
if err := binary.Read(r, binary.BigEndian, &header.Signature); err != nil {
return nil, fmt.Errorf("error reading signature: %v", err)
}
if err := binary.Read(r, binary.BigEndian, &header.Version); err != nil {
return nil, fmt.Errorf("error reading version: %v", err)
}
if err := binary.Read(r, binary.BigEndian, &header.Flags); err != nil {
return nil, fmt.Errorf("error reading flags: %v", err)
}
if err := binary.Read(r, binary.BigEndian, &header.DataOffset); err != nil {
return nil, fmt.Errorf("error reading data offset: %v", err)
}
// Validate FLV signature
if string(header.Signature[:]) != "FLV" {
return nil, fmt.Errorf("invalid FLV signature: %s", string(header.Signature[:]))
}
fmt.Printf("FLV Header: Version=%d, Flags=%d, DataOffset=%d\n", header.Version, header.Flags, header.DataOffset)
return header, nil
}
func readFLVTag(r io.Reader) (*FLVTag, error) {
tag := &FLVTag{}
// Read previous tag size (4 bytes)
var prevTagSize uint32
if err := binary.Read(r, binary.BigEndian, &prevTagSize); err != nil {
return nil, err
}
fmt.Printf("Previous tag size: %d\n", prevTagSize)
// Read tag type (1 byte)
if err := binary.Read(r, binary.BigEndian, &tag.TagType); err != nil {
return nil, err
}
fmt.Printf("Tag type: %d\n", tag.TagType)
// Read data size (3 bytes)
var dataSize [3]byte
if _, err := io.ReadFull(r, dataSize[:]); err != nil {
return nil, err
}
tag.DataSize = uint32(dataSize[0])<<16 | uint32(dataSize[1])<<8 | uint32(dataSize[2])
fmt.Printf("Data size: %d\n", tag.DataSize)
// Read timestamp (3 bytes + 1 byte extended)
var timestamp [3]byte
if _, err := io.ReadFull(r, timestamp[:]); err != nil {
return nil, err
}
var timestampExtended uint8
if err := binary.Read(r, binary.BigEndian, &timestampExtended); err != nil {
return nil, err
}
tag.Timestamp = uint32(timestamp[0])<<16 | uint32(timestamp[1])<<8 | uint32(timestamp[2]) | uint32(timestampExtended)<<24
fmt.Printf("Timestamp: %d\n", tag.Timestamp)
// Read stream ID (3 bytes)
var streamID [3]byte
if _, err := io.ReadFull(r, streamID[:]); err != nil {
return nil, err
}
tag.StreamID = uint32(streamID[0])<<16 | uint32(streamID[1])<<8 | uint32(streamID[2])
fmt.Printf("Stream ID: %d\n", tag.StreamID)
// Read tag data
tag.Data = make([]byte, tag.DataSize)
if _, err := io.ReadFull(r, tag.Data); err != nil {
return nil, err
}
fmt.Printf("Read tag data of size %d\n", len(tag.Data))
return tag, nil
}
func findBoxOffsets(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
type boxInfo struct {
name string
offset int64
size uint32
}
var boxes []boxInfo
// Read the entire file
data, err := io.ReadAll(file)
if err != nil {
return err
}
// Search for boxes
var i int
for i < len(data)-8 {
// Read box size (4 bytes) and type (4 bytes)
size := binary.BigEndian.Uint32(data[i : i+4])
boxType := string(data[i+4 : i+8])
// Validate box size
if size < 8 || int64(size) > int64(len(data))-int64(i) {
i++
continue
}
if boxType == "tfdt" || boxType == "mdat" || boxType == "moof" || boxType == "traf" {
boxes = append(boxes, boxInfo{
name: boxType,
offset: int64(i),
size: size,
})
// Print the entire box content for small boxes
if size <= 256 {
fmt.Printf("\nFull %s box at offset %d (0x%x):\n", boxType, i, i)
// Print box header
fmt.Printf("Header: % x\n", data[i:i+8])
// Print content in chunks of 32 bytes
for j := i + 8; j < i+int(size); j += 32 {
end := j + 32
if end > i+int(size) {
end = i + int(size)
}
fmt.Printf("Content [%d-%d]: % x\n", j-i, end-i, data[j:end])
}
}
}
// Move to the next box
i += int(size)
}
// Print box information in order of appearance
fmt.Println("\nBox layout:")
for _, box := range boxes {
fmt.Printf("%s box at offset %d (0x%x), size: %d bytes\n",
box.name, box.offset, box.offset, box.size)
// Print the first few bytes of the box content
start := box.offset + 8 // skip size and type
end := start + 32
if end > box.offset+int64(box.size) {
end = box.offset + int64(box.size)
}
fmt.Printf("%s content: % x\n", box.name, data[start:end])
// For tfdt box, also print the previous and next 8 bytes
if box.name == "tfdt" {
prevStart := box.offset - 8
if prevStart < 0 {
prevStart = 0
}
nextEnd := box.offset + int64(box.size) + 8
if nextEnd > int64(len(data)) {
nextEnd = int64(len(data))
}
fmt.Printf("Context around tfdt:\n")
fmt.Printf("Previous 8 bytes: % x\n", data[prevStart:box.offset])
fmt.Printf("Next 8 bytes: % x\n", data[box.offset+int64(box.size):nextEnd])
}
}
return nil
}
func TestFLVToFMP4(t *testing.T) {
func TestFLVToMP4(t *testing.T) {
// Open FLV file
flvFile, err := os.Open("/Users/dexter/Movies/frame_counter_4k_60fps.flv")
if err != nil {
@@ -241,16 +18,17 @@ func TestFLVToFMP4(t *testing.T) {
}
defer flvFile.Close()
// Create output FMP4 file
outFile, err := os.Create("test.mp4")
// Create output MP4 file
outFile, err := os.Create("test_regular.mp4")
if err != nil {
t.Fatalf("Failed to create output file: %v", err)
}
defer outFile.Close()
// Create FMP4 muxer
muxer := NewMuxer(FLAG_FRAGMENT)
// Create regular MP4 muxer (without fragmentation flag)
muxer := NewMuxer(0) // No flags for regular MP4
muxer.WriteInitSegment(outFile)
// Read FLV header
header, err := readFLVHeader(flvFile)
if err != nil {
@@ -258,6 +36,7 @@ func TestFLVToFMP4(t *testing.T) {
}
hasVideo := header.Flags&0x01 != 0
hasAudio := header.Flags&0x04 != 0
// Skip to the first tag
if _, err := flvFile.Seek(int64(header.DataOffset), io.SeekStart); err != nil {
@@ -265,20 +44,23 @@ func TestFLVToFMP4(t *testing.T) {
}
// Create tracks
var videoTrack *Track
var videoTrack, audioTrack *Track
if hasVideo {
videoTrack = muxer.AddTrack(box.MP4_CODEC_H264)
videoTrack.Width = 3840 // 4K resolution
videoTrack.Height = 2160
videoTrack.Timescale = 1000
}
if hasAudio {
audioTrack = muxer.AddTrack(box.MP4_CODEC_AAC)
audioTrack.Timescale = 1000
}
// Variables to store codec configuration
var videoConfig []byte
var frameCount int
var videoConfig, audioConfig []byte
var frameCount, sampleCount int
// Process FLV tags
TagLoop:
for {
tag, err := readFLVTag(flvFile)
if err != nil {
@@ -289,6 +71,39 @@ TagLoop:
}
switch tag.TagType {
case 8: // Audio
if !hasAudio || audioTrack == nil {
continue
}
soundFormat := tag.Data[0] >> 4
if soundFormat != 10 { // AAC
continue
}
aacPacketType := tag.Data[1]
if aacPacketType == 0 { // AAC sequence header
fmt.Println("Found AAC sequence header")
audioConfig = tag.Data[2:] // Store AAC config
audioTrack.ExtraData = audioConfig
} else if len(audioConfig) > 0 { // Audio data
if len(tag.Data) <= 2 {
fmt.Printf("Skipping empty audio sample at timestamp %d\n", tag.Timestamp)
continue
}
sample := box.Sample{
Data: tag.Data[2:],
DTS: uint64(tag.Timestamp),
PTS: uint64(tag.Timestamp),
KeyFrame: true, // Audio samples are always key frames
}
if err := muxer.WriteSample(outFile, audioTrack, sample); err != nil {
t.Fatalf("Failed to write audio sample: %v", err)
}
sampleCount++
}
case 9: // Video
if !hasVideo || videoTrack == nil {
continue
@@ -309,12 +124,11 @@ TagLoop:
// Read composition time offset (24 bits, signed)
compositionTime := int32(tag.Data[2])<<16 | int32(tag.Data[3])<<8 | int32(tag.Data[4])
// Convert 24-bit signed integer to 32-bit signed integer
if compositionTime&0x800000 != 0 {
compositionTime |= ^0xffffff
}
// 验证和修复 AVCC 格式
// Validate and fix AVCC format
validData, err := validateAndFixAVCC(tag.Data[5:])
if err != nil {
fmt.Printf("Warning: Invalid AVCC data at timestamp %d: %v\n", tag.Timestamp, err)
@@ -331,35 +145,20 @@ TagLoop:
t.Fatalf("Failed to write video sample: %v", err)
}
frameCount++
if frameCount >= 5 {
fmt.Println("Wrote 5 frames, stopping")
break TagLoop
}
}
}
}
}
// Create sample table boxes before writing trailer
if videoTrack != nil {
videoTrack.makeStblBox()
}
// Write trailer
if err := muxer.WriteTrailer(outFile); err != nil {
t.Fatalf("Failed to write trailer: %v", err)
}
fmt.Println("Conversion completed successfully")
// Find and analyze box positions
if err := findBoxOffsets("test.mp4"); err != nil {
t.Fatalf("Failed to analyze boxes: %v", err)
}
fmt.Printf("Conversion completed successfully with %d video frames and %d audio samples\n", frameCount, sampleCount)
// Validate the generated MP4 file using MP4Box
cmd := exec.Command("MP4Box", "-info", "test.mp4")
cmd := exec.Command("MP4Box", "-info", "test_regular.mp4")
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("MP4Box validation failed: %v\nOutput: %s", err, output)

View File

@@ -1,8 +1,8 @@
package mp4
import (
"fmt"
"io"
"slices"
. "m7s.live/v5/plugin/mp4/pkg/box"
)
@@ -12,40 +12,40 @@ type (
Cid MP4_CODEC_TYPE
TrackId uint32
SampleTable
Duration uint32
Height uint32
Width uint32
SampleRate uint32
SampleSize uint16
SampleCount uint32
ChannelCount uint8
Timescale uint32
StartDts uint64
EndDts uint64
StartPts uint64
EndPts uint64
Samplelist []Sample
ELST *EditListBox
ExtraData []byte
writer io.WriteSeeker
fragments []Fragment
defaultSize uint32
defaultDuration uint32
defaultSampleFlags uint32
baseDataOffset uint64
stbl []byte
Duration uint32
Height uint32
Width uint32
SampleRate uint32
SampleSize uint16
SampleCount uint32
ChannelCount uint8
Timescale uint32
// StartDts uint64
// EndDts uint64
// StartPts uint64
// EndPts uint64
Samplelist []Sample
ELST *EditListBox
ExtraData []byte
writer io.WriteSeeker
fragments []Fragment
defaultSize uint32
defaultDuration uint32
// defaultSampleFlags uint32
// baseDataOffset uint64
// stbl []byte
FragmentSequenceNumber uint32
//for subsample
defaultIsProtected uint8
defaultPerSampleIVSize uint8
defaultCryptByteBlock uint8
defaultSkipByteBlock uint8
defaultConstantIV []byte
defaultKID [16]byte
lastSeig *SeigSampleGroupEntry
lastSaiz *SaizBox
subSamples []SencEntry
// defaultIsProtected uint8
// defaultPerSampleIVSize uint8
// defaultCryptByteBlock uint8
// defaultSkipByteBlock uint8
// defaultConstantIV []byte
// defaultKID [16]byte
// lastSeig *SeigSampleGroupEntry
// lastSaiz *SaizBox
// subSamples []SencEntry
}
Fragment struct {
Offset uint64
@@ -57,7 +57,7 @@ type (
}
)
func (track *Track) makeElstBox() []byte {
func (track *Track) makeElstBox() *EditListBox {
delay := track.Samplelist[0].PTS * 1000 / uint64(track.Timescale)
entryCount := 1
version := byte(0)
@@ -71,23 +71,23 @@ func (track *Track) makeElstBox() []byte {
// entryCount += 1
// }
boxSize += 4 + entrySize*entryCount
elst := NewEditListBox(version)
elst.Entrys = make([]ELSTEntry, entryCount)
entrys := make([]ELSTEntry, entryCount)
// if entryCount > 1 {
// elst.entrys.entrys[0].segmentDuration = startCt
// elst.entrys.entrys[0].mediaTime = -1
// elst.entrys.entrys[0].mediaRateInteger = 0x0001
// elst.entrys.entrys[0].mediaRateFraction = 0
// }
//简单起见mediaTime先固定为0,即不延迟播放
elst.Entrys[entryCount-1].SegmentDuration = uint64(track.Duration)
elst.Entrys[entryCount-1].MediaTime = 0
elst.Entrys[entryCount-1].MediaRateInteger = 0x0001
elst.Entrys[entryCount-1].MediaRateFraction = 0
entrys[entryCount-1].SegmentDuration = uint64(track.Duration)
entrys[entryCount-1].MediaTime = 0
entrys[entryCount-1].MediaRateInteger = 0x0001
entrys[entryCount-1].MediaRateFraction = 0
_, boxdata := elst.Encode(boxSize)
return boxdata
return CreateEditListBox(version, entrys)
}
@@ -106,12 +106,8 @@ func (track *Track) Seek(dts uint64) int {
return -1
}
func (track *Track) makeEdtsBox() []byte {
elst := track.makeElstBox()
edts := BasicBox{Type: TypeEDTS, Size: 8 + uint64(len(elst))}
offset, edtsbox := edts.Encode()
copy(edtsbox[offset:], elst)
return edtsbox
func (track *Track) makeEdtsBox() *ContainerBox {
return CreateContainerBox(TypeEDTS, track.makeElstBox())
}
func (track *Track) AddSampleEntry(entry Sample) {
@@ -128,138 +124,90 @@ func (track *Track) AddSampleEntry(entry Sample) {
track.Samplelist = append(track.Samplelist, entry)
}
func (track *Track) makeTkhdBox() []byte {
tkhd := NewTrackHeaderBox()
func (track *Track) makeTkhdBox() *TrackHeaderBox {
tkhd := CreateTrackHeaderBox(track.TrackId, uint64(track.Duration), track.Width, track.Height)
tkhd.Duration = uint64(track.Duration)
tkhd.Track_ID = track.TrackId
tkhd.TrackID = track.TrackId
if track.Cid == MP4_CODEC_AAC || track.Cid == MP4_CODEC_G711A || track.Cid == MP4_CODEC_G711U || track.Cid == MP4_CODEC_OPUS {
tkhd.Volume = 0x0100
} else {
tkhd.Width = track.Width << 16
tkhd.Height = track.Height << 16
}
_, tkhdbox := tkhd.Encode()
return tkhdbox
return tkhd
}
func (track *Track) makeMinfBox() []byte {
var mhdbox []byte
func (track *Track) makeMinfBox() *ContainerBox {
var mhdbox IBox
switch track.Cid {
case MP4_CODEC_H264, MP4_CODEC_H265:
mhdbox = MakeVmhdBox()
mhdbox = CreateVideoMediaHeaderBox()
case MP4_CODEC_G711A, MP4_CODEC_G711U, MP4_CODEC_AAC,
MP4_CODEC_MP2, MP4_CODEC_MP3, MP4_CODEC_OPUS:
mhdbox = MakeSmhdBox()
mhdbox = CreateSoundMediaHeaderBox()
default:
panic("unsupport codec id")
}
dinfbox := MakeDefaultDinfBox()
dinfbox := CreateDataInformationBox()
stblbox := track.makeStblBox()
minf := BasicBox{Type: TypeMINF, Size: 8 + uint64(len(mhdbox)+len(dinfbox)+len(stblbox))}
offset, minfbox := minf.Encode()
copy(minfbox[offset:], mhdbox)
offset += len(mhdbox)
copy(minfbox[offset:], dinfbox)
offset += len(dinfbox)
copy(minfbox[offset:], stblbox)
offset += len(stblbox)
return minfbox
return CreateContainerBox(TypeMINF, mhdbox, dinfbox, stblbox)
}
func (track *Track) makeMdiaBox() []byte {
mdhdbox := MakeMdhdBox(track.Duration)
func (track *Track) makeMdiaBox() *ContainerBox {
mdhdbox := CreateMediaHeaderBox(track.Timescale, uint64(track.Duration))
hdlrbox := MakeHdlrBox(GetHandlerType(track.Cid))
minfbox := track.makeMinfBox()
mdia := BasicBox{Type: TypeMDIA, Size: 8 + uint64(len(mdhdbox)+len(hdlrbox)+len(minfbox))}
offset, mdiabox := mdia.Encode()
copy(mdiabox[offset:], mdhdbox)
offset += len(mdhdbox)
copy(mdiabox[offset:], hdlrbox)
offset += len(hdlrbox)
copy(mdiabox[offset:], minfbox)
offset += len(minfbox)
return mdiabox
return CreateContainerBox(TypeMDIA, mdhdbox, hdlrbox, minfbox)
}
func (track *Track) makeStblBox() []byte {
var stsdbox, sttsbox, cttsbox, stscbox, stszbox, stcobox, stssbox []byte
stsdbox = track.makeStsd(GetHandlerType(track.Cid))
if track.SampleTable.STTS != nil {
_, sttsbox = track.SampleTable.STTS.Encode()
}
if track.SampleTable.CTTS != nil {
_, cttsbox = track.SampleTable.CTTS.Encode()
}
if track.SampleTable.STSC != nil {
_, stscbox = track.SampleTable.STSC.Encode()
}
if track.SampleTable.STSZ != nil {
_, stszbox = track.SampleTable.STSZ.Encode()
}
if track.SampleTable.STCO != nil {
_, stcobox = track.SampleTable.STCO.Encode()
}
func (track *Track) makeStblBox() IBox {
track.STSD = track.makeStsd(GetHandlerType(track.Cid))
if track.Cid == MP4_CODEC_H264 || track.Cid == MP4_CODEC_H265 {
stssbox = track.makeStssBox()
track.STSS = track.makeStssBox()
}
stbl := BasicBox{Type: TypeSTBL, Size: uint64(8 + len(stsdbox) + len(sttsbox) + len(cttsbox) + len(stscbox) + len(stszbox) + len(stcobox) + len(stssbox))}
offset, stblbox := stbl.Encode()
copy(stblbox[offset:], stsdbox)
offset += len(stsdbox)
copy(stblbox[offset:], sttsbox)
offset += len(sttsbox)
copy(stblbox[offset:], cttsbox)
offset += len(cttsbox)
copy(stblbox[offset:], stscbox)
offset += len(stscbox)
copy(stblbox[offset:], stszbox)
offset += len(stszbox)
copy(stblbox[offset:], stcobox)
offset += len(stcobox)
copy(stblbox[offset:], stssbox)
offset += len(stssbox)
return stblbox
return CreateContainerBox(TypeSTBL, track.STSD, track.STSS, track.STTS, track.CTTS, track.STSC, track.STSZ, track.STCO)
}
func (track *Track) makeStsd(handler_type HandlerType) []byte {
var avbox []byte
func (track *Track) makeStsd(handler_type HandlerType) *STSDBox {
var avbox IBox
if track.Cid == MP4_CODEC_H264 {
avbox = MakeAvcCBox(track.ExtraData)
avbox = CreateDataBox(TypeAVCC, track.ExtraData)
} else if track.Cid == MP4_CODEC_H265 {
avbox = MakeHvcCBox(track.ExtraData)
avbox = CreateDataBox(TypeHVCC, track.ExtraData)
} else if track.Cid == MP4_CODEC_AAC || track.Cid == MP4_CODEC_MP2 || track.Cid == MP4_CODEC_MP3 {
avbox = MakeEsdsBox(track.TrackId, track.Cid, track.ExtraData)
avbox = CreateESDSBox(uint16(track.TrackId), track.Cid, track.ExtraData)
} else if track.Cid == MP4_CODEC_OPUS {
avbox = MakeOpusSpecificBox(track.ExtraData)
avbox = CreateOpusSpecificBox(track.ExtraData)
}
var se []byte
var offset int
var entry IBox
if handler_type == TypeVIDE {
entry := NewVisualSampleEntry(GetCodecNameWithCodecId(track.Cid))
entry.Width = uint16(track.Width)
entry.Height = uint16(track.Height)
offset, se = entry.Encode(entry.Size() + uint64(len(avbox)))
entry = CreateVisualSampleEntry(GetCodecNameWithCodecId(track.Cid), uint16(track.Width), uint16(track.Height), avbox)
} else if handler_type == TypeSOUN {
entry := NewAudioSampleEntry(GetCodecNameWithCodecId(track.Cid))
entry.ChannelCount = uint16(track.ChannelCount)
entry.Samplerate = track.SampleRate
entry.SampleSize = track.SampleSize
offset, se = entry.Encode(entry.Size() + uint64(len(avbox)))
entry = CreateAudioSampleEntry(GetCodecNameWithCodecId(track.Cid), uint16(track.ChannelCount), uint16(track.SampleSize), track.SampleRate, avbox)
}
copy(se[offset:], avbox)
var stsd SampleDescriptionBox = 1
offset2, stsdbox := stsd.Encode(FullBoxLen + 4 + uint64(len(se)))
copy(stsdbox[offset2:], se)
return stsdbox
return CreateSTSDBox(entry)
}
// fmp4
func (track *Track) makeTraf(dataOffsetOffset *int) []byte {
func (track *Track) makeTraf() *TrackFragmentBox {
// Create tfhd box
tfhd := track.makeTfhdBox()
// Create tfdt box
tfdt := track.makeTfdtBox()
// Create trun box with all samples
trun := track.makeTrunBox(0, len(track.Samplelist))
// Create track fragment box with all necessary boxes
traf := CreateTrackFragmentBox(tfhd, tfdt, trun)
return traf
}
func (track *Track) makeTfhdBox() *TrackFragmentHeaderBox {
tfFlags := uint32(0)
tfFlags |= TF_FLAG_DEFAULT_BASE_IS_MOOF
tfFlags |= TF_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT
@@ -267,8 +215,7 @@ func (track *Track) makeTraf(dataOffsetOffset *int) []byte {
tfFlags |= TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT
tfFlags |= TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT
tfhd := NewTrackFragmentHeaderBox(track.TrackId)
tfhd.SampleDescriptionIndex = 1
tfhd := CreateTrackFragmentHeaderBox(track.TrackId, tfFlags)
// Calculate default sample duration
if len(track.Samplelist) > 1 {
@@ -297,175 +244,37 @@ func (track *Track) makeTraf(dataOffsetOffset *int) []byte {
} else {
tfhd.DefaultSampleFlags = MOV_FRAG_SAMPLE_FLAG_DEPENDS_NO
}
// Create tfdt box
tfdt := NewTrackFragmentBaseMediaDecodeTimeBox(uint64(track.Samplelist[0].DTS))
// Calculate traf size first (including header)
_, tfhdbox := tfhd.Encode(tfFlags)
_, tfdtbox := tfdt.Encode()
trafSize := uint64(8) + uint64(len(tfhdbox)) + uint64(len(tfdtbox))
// Create trun box with total moof size
// moof = header(8) + mfhd(16) + traf
// traf = header(8) + tfhd + tfdt + trun
// So the data offset should be relative to moof start
trun := track.makeTrunBox(0, len(track.Samplelist))
// Update traf size with trun size
trafSize += uint64(len(trun))
// Create traf box
traf := BasicBox{Type: TypeTRAF, Size: trafSize}
offset, boxData := traf.Encode()
copy(boxData[offset:], tfhdbox)
offset += len(tfhdbox)
copy(boxData[offset:], tfdtbox)
offset += len(tfdtbox)
copy(boxData[offset:], trun)
*dataOffsetOffset = offset + 12 + 4 // 12 for trun header, 4 for trun sample count
offset += len(trun)
if offset != int(trafSize) {
panic("traf box size mismatch")
}
return boxData
return tfhd
}
func (track *Track) makeTfhdBox(offset uint64) []byte {
// Set flags in the correct order
tfFlags := uint32(0)
// Set base is moof flag (0x020000)
tfFlags |= TF_FLAG_DEFAULT_BASE_IS_MOOF
// Then set sample description index flag (0x000002)
tfFlags |= TF_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT
// Then set default sample duration flag (0x000008)
if len(track.Samplelist) > 0 {
// Calculate average duration
var totalDuration uint64 = 0
var count int = 0
for i := 1; i < len(track.Samplelist); i++ {
duration := track.Samplelist[i].DTS - track.Samplelist[i-1].DTS
if duration > 0 {
totalDuration += duration
count++
}
}
if count > 0 {
tfFlags |= TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT
} else if len(track.fragments) > 0 {
lastFrag := track.fragments[len(track.fragments)-1]
if lastFrag.Duration > 0 {
tfFlags |= TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT
}
}
}
// Then set default sample size flag (0x000010)
if len(track.Samplelist) > 0 {
tfFlags |= TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT
}
// Then set default sample flags flag (0x000020)
tfFlags |= TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT
// Create tfhd box
tfhd := NewTrackFragmentHeaderBox(track.TrackId)
// Set default values based on flags
if tfFlags&TF_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT != 0 {
if len(track.Samplelist) > 1 {
var totalDuration uint64 = 0
var count int = 0
for i := 1; i < len(track.Samplelist); i++ {
duration := track.Samplelist[i].DTS - track.Samplelist[i-1].DTS
if duration > 0 {
totalDuration += duration
count++
}
}
if count > 0 {
tfhd.DefaultSampleDuration = uint32(totalDuration / uint64(count))
}
} else if len(track.fragments) > 0 {
lastFrag := track.fragments[len(track.fragments)-1]
if lastFrag.Duration > 0 {
tfhd.DefaultSampleDuration = lastFrag.Duration
}
}
}
if tfFlags&TF_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT != 0 {
tfhd.DefaultSampleSize = uint32(track.Samplelist[0].Size)
}
if tfFlags&TF_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT != 0 {
if track.Cid.IsVideo() {
tfhd.DefaultSampleFlags = MOV_FRAG_SAMPLE_FLAG_DEPENDS_YES | MOV_FRAG_SAMPLE_FLAG_IS_NON_SYNC
} else {
tfhd.DefaultSampleFlags = MOV_FRAG_SAMPLE_FLAG_DEPENDS_NO
}
}
// Store default values for later use
track.defaultDuration = tfhd.DefaultSampleDuration
track.defaultSize = tfhd.DefaultSampleSize
track.defaultSampleFlags = tfhd.DefaultSampleFlags
// Print tfhd box details
fmt.Printf("tfhd box: flags=0x%08x, track_id=%d\n", tfFlags, track.TrackId)
fmt.Printf("tfhd box: default_sample_duration=%d, default_sample_size=%d, default_sample_flags=0x%08x\n",
tfhd.DefaultSampleDuration, tfhd.DefaultSampleSize, tfhd.DefaultSampleFlags)
_, boxData := tfhd.Encode(tfFlags)
fmt.Printf("tfhd box first 32 bytes: % 02X\n", boxData[:32])
return boxData
func (track *Track) makeTfdtBox() *TrackFragmentBaseMediaDecodeTimeBox {
return CreateTrackFragmentBaseMediaDecodeTimeBox(uint64(track.Samplelist[0].DTS))
}
func (track *Track) makeTfdtBox() []byte {
tfdt := NewTrackFragmentBaseMediaDecodeTimeBox(uint64(track.Samplelist[0].DTS))
offset, boxData := tfdt.Encode()
expectedSize := tfdt.Size()
if uint64(offset) != expectedSize {
panic("tfdt box size is wrong")
}
return boxData[:offset]
}
func (track *Track) makeStssBox() (boxdata []byte) {
var stss SyncSampleBox
func (track *Track) makeStssBox() *STSSBox {
var stss []uint32
for i, sample := range track.Samplelist {
if sample.KeyFrame {
stss = append(stss, uint32(i+1))
}
}
_, boxdata = stss.Encode()
return
return CreateSTSSBox(stss)
}
func (track *Track) makeTfraBox() []byte {
tfra := NewTrackFragmentRandomAccessBox(track.TrackId)
tfra.LengthSizeOfSampleNum = 0
tfra.LengthSizeOfTrafNum = 0
tfra.LengthSizeOfTrunNum = 0
for _, f := range track.fragments {
tfra.FragEntrys = append(tfra.FragEntrys, FragEntry{
Time: f.FirstPts,
MoofOffset: f.Offset,
})
}
_, tfraData := tfra.Encode()
return tfraData
func (track *Track) makeTfraBox() *TrackFragmentRandomAccessBox {
return CreateTrackFragmentRandomAccessBox(track.TrackId, slices.Collect(func(yield func(TFRAEntry) bool) {
for _, f := range track.fragments {
if !yield(TFRAEntry{
Time: f.FirstPts,
MoofOffset: f.Offset,
}) {
break
}
}
}))
}
func (track *Track) makeTrunBox(start, end int) []byte {
// Create a new TrackRunBox
trun := NewTrackRunBox()
trun.SampleCount = uint32(end - start)
func (track *Track) makeTrunBox(start, end int) *TrackRunBox {
// Set flags in the correct order
flag := TR_FLAG_DATA_OFFSET
@@ -480,6 +289,7 @@ func (track *Track) makeTrunBox(start, end int) []byte {
if track.Cid.IsVideo() {
flag |= TR_FLAG_DATA_SAMPLE_FLAGS
}
// Create a new TrackRunBox
// Finally set composition time offset flag if needed (0x000800)
for i := start; i < end; i++ {
@@ -488,52 +298,44 @@ func (track *Track) makeTrunBox(start, end int) []byte {
break
}
}
trun := CreateTrackRunBox(flag, uint32(end-start))
// Fill entry list
trun.EntryList = make([]TrunEntry, trun.SampleCount)
trun.Entries = make([]TrunEntry, trun.SampleCount)
for i := 0; i < int(trun.SampleCount); i++ {
sample := &track.Samplelist[start+i]
// Duration
if i < int(trun.SampleCount)-1 {
trun.EntryList[i].SampleDuration = uint32(track.Samplelist[start+i+1].DTS - sample.DTS)
trun.Entries[i].SampleDuration = uint32(track.Samplelist[start+i+1].DTS - sample.DTS)
} else {
trun.EntryList[i].SampleDuration = trun.EntryList[i-1].SampleDuration
trun.Entries[i].SampleDuration = trun.Entries[i-1].SampleDuration
}
// Size
trun.EntryList[i].SampleSize = uint32(sample.Size)
trun.Entries[i].SampleSize = uint32(sample.Size)
// Flags
if flag&TR_FLAG_DATA_SAMPLE_FLAGS != 0 {
if sample.KeyFrame {
trun.EntryList[i].SampleFlags = MOV_FRAG_SAMPLE_FLAG_DEPENDS_NO | MOV_FRAG_SAMPLE_FLAG_IS_SYNC
trun.Entries[i].SampleFlags = MOV_FRAG_SAMPLE_FLAG_DEPENDS_NO | MOV_FRAG_SAMPLE_FLAG_IS_SYNC
} else {
trun.EntryList[i].SampleFlags = MOV_FRAG_SAMPLE_FLAG_DEPENDS_YES | MOV_FRAG_SAMPLE_FLAG_IS_NON_SYNC
trun.Entries[i].SampleFlags = MOV_FRAG_SAMPLE_FLAG_DEPENDS_YES | MOV_FRAG_SAMPLE_FLAG_IS_NON_SYNC
}
}
// Composition time offset
if flag&TR_FLAG_DATA_SAMPLE_COMPOSITION_TIME != 0 {
trun.EntryList[i].SampleCompositionTimeOffset = int32(sample.PTS - sample.DTS)
trun.Entries[i].SampleCompositionTimeOffset = int32(sample.PTS - sample.DTS)
}
}
// Calculate data offset
// Data offset is relative to the start of the moof box
// When TF_FLAG_DEFAULT_BASE_IS_MOOF is set, we need to add the size of the moof box
// to point to the start of the data in the mdat box
// trun.Dataoffset = int32(moofSize + 8) // +8 for mdat header
// Print trun box details
_, boxData := trun.Encode(flag)
return boxData
return trun
}
func (track *Track) makeStblTable() {
sameSize := true
movchunks := make([]movchunk, 0)
ckn := uint32(0)
var stts TimeToSampleBox
var ctts CompositionOffsetBox
var stco ChunkOffsetBox
var stts []STTSEntry
var ctts []CTTSEntry
var stco []uint64
for i, sample := range track.Samplelist {
sttsEntry := STTSEntry{SampleCount: 1, SampleDelta: 1}
cttsEntry := CTTSEntry{SampleCount: 1, SampleOffset: uint32(sample.PTS) - uint32(sample.DTS)}
@@ -574,56 +376,56 @@ func (track *Track) makeStblTable() {
ckn++
}
}
stsz := &SampleSizeBox{
SampleSize: 0,
SampleCount: uint32(len(track.Samplelist)),
}
var sampleSize uint32
var entrySizelist []uint32
if sameSize {
stsz.SampleSize = uint32(track.Samplelist[0].Size)
sampleSize = uint32(track.Samplelist[0].Size)
} else {
stsz.EntrySizelist = make([]uint32, stsz.SampleCount)
for i := 0; i < len(stsz.EntrySizelist); i++ {
stsz.EntrySizelist[i] = uint32(track.Samplelist[i].Size)
entrySizelist = make([]uint32, len(track.Samplelist))
for i := 0; i < len(track.Samplelist); i++ {
entrySizelist[i] = uint32(track.Samplelist[i].Size)
}
}
var stsc SampleToChunkBox
var stsc []STSCEntry
for i, chunk := range movchunks {
if i == 0 || chunk.samplenum != movchunks[i-1].samplenum {
stsc = append(stsc, STSCEntry{FirstChunk: chunk.chunknum + 1, SampleDescriptionIndex: 1, SamplesPerChunk: chunk.samplenum})
}
}
track.SampleTable.STTS = &stts
track.SampleTable.STSC = &stsc
track.SampleTable.STCO = &stco
track.SampleTable.STSZ = stsz
if track.Cid == MP4_CODEC_H264 || track.Cid == MP4_CODEC_H265 {
track.SampleTable.CTTS = &ctts
track.CTTS = CreateCTTSBox(ctts)
}
track.STTS = CreateSTTSBox(stts)
track.STSC = CreateSTSCBox(stsc)
track.STCO = CreateSTCOBox(stco)
track.STSZ = CreateSTSZBox(sampleSize, entrySizelist)
}
func (track *Track) makeSidxBox(totalSidxSize uint32, refsize uint32) []byte {
sidx := NewSegmentIndexBox()
sidx.ReferenceID = track.TrackId
sidx.TimeScale = track.Timescale
sidx.EarliestPresentationTime = track.StartPts
sidx.ReferenceCount = 1
sidx.FirstOffset = 52 + uint64(totalSidxSize)
entry := SidxEntry{
ReferenceType: 0,
ReferencedSize: refsize,
SubsegmentDuration: 0,
StartsWithSAP: 1,
SAPType: 0,
SAPDeltaTime: 0,
}
// func (track *Track) makeSidxBox(totalSidxSize uint32, refsize uint32) []byte {
// sidx := NewSegmentIndexBox()
// sidx.ReferenceID = track.TrackId
// sidx.TimeScale = track.Timescale
// sidx.EarliestPresentationTime = track.StartPts
// sidx.ReferenceCount = 1
// sidx.FirstOffset = 52 + uint64(totalSidxSize)
// entry := SidxEntry{
// ReferenceType: 0,
// ReferencedSize: refsize,
// SubsegmentDuration: 0,
// StartsWithSAP: 1,
// SAPType: 0,
// SAPDeltaTime: 0,
// }
if len(track.Samplelist) > 0 {
entry.SubsegmentDuration = uint32(track.Samplelist[len(track.Samplelist)-1].DTS) - uint32(track.StartDts)
}
sidx.Entrys = append(sidx.Entrys, entry)
sidx.Box.Box.Size = sidx.Size()
_, boxData := sidx.Encode()
return boxData
}
// if len(track.Samplelist) > 0 {
// entry.SubsegmentDuration = uint32(track.Samplelist[len(track.Samplelist)-1].DTS) - uint32(track.StartDts)
// }
// sidx.Entrys = append(sidx.Entrys, entry)
// sidx.Box.Box.Size = sidx.Size()
// _, boxData := sidx.Encode()
// return boxData
// }