mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-09-26 23:05:55 +08:00
in progress
This commit is contained in:
@@ -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
|
||||
|
@@ -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 {
|
||||
|
85
plugin/mp4/box_structure.md
Normal file
85
plugin/mp4/box_structure.md
Normal 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)
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
132
plugin/mp4/pkg/box/elst.go
Normal 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)
|
||||
}
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
85
plugin/mp4/pkg/box/moof.go
Normal file
85
plugin/mp4/pkg/box/moof.go
Normal 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)
|
||||
}
|
70
plugin/mp4/pkg/box/moov.go
Normal file
70
plugin/mp4/pkg/box/moov.go
Normal 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)
|
||||
}
|
@@ -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
|
||||
}
|
56
plugin/mp4/pkg/box/mvex.go
Normal file
56
plugin/mp4/pkg/box/mvex.go
Normal 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)
|
||||
}
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
153
plugin/mp4/pkg/box/trak.go
Normal 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)
|
||||
}
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
359
plugin/mp4/pkg/muxer_fmp4_test.go
Normal file
359
plugin/mp4/pkg/muxer_fmp4_test.go
Normal 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, ×tampExtended); 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")
|
||||
}
|
@@ -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, ×tampExtended); 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)
|
||||
|
@@ -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
|
||||
// }
|
||||
|
Reference in New Issue
Block a user