From 13d151545c747be83f1b253a7563906d59decfb7 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 19 Nov 2015 08:19:14 +0800 Subject: [PATCH 01/93] first blood --- atom/atom.go | 116 +++++++++++++++++ atom/genAtomStruct.js | 294 ++++++++++++++++++++++++++++++++++++++++++ atom/reader.go | 86 ++++++++++++ atom/struct.go | 134 +++++++++++++++++++ avcc.go | 17 +++ mp4.go | 60 +++++++++ mp4a.go | 15 +++ 7 files changed, 722 insertions(+) create mode 100644 atom/atom.go create mode 100644 atom/genAtomStruct.js create mode 100644 atom/reader.go create mode 100644 atom/struct.go create mode 100644 avcc.go create mode 100644 mp4.go create mode 100644 mp4a.go diff --git a/atom/atom.go b/atom/atom.go new file mode 100644 index 0000000..8cf5f58 --- /dev/null +++ b/atom/atom.go @@ -0,0 +1,116 @@ + +package atom + +import ( + "log" +) + +type Atom interface { + CC4() string + Read(Reader) error +} + +type Ftyp struct { +} + +func (self Ftyp) CC4() string { + return "ftyp" +} + +func (self Ftyp) Read(r Reader) (err error) { + log.Println("read ftyp") + return +} + +type Moov struct { + *Mvhd + Trak []*Trak +} + +func (self Moov) CC4() string { + return "moov" +} + +func (self *Moov) Read(r Reader) (err error) { + var atom Atom + if atom, err = r.ReadAtom(&Mvhd{}); err != nil { + return + } + self.Mvhd = atom.(*Mvhd) + + for { + if atom, err := r.ReadAtom(&Trak{}); err != nil { + break + } else { + self.Trak = append(self.Trak, atom.(*Trak)) + } + } + return +} + +type Mvhd struct { +} + +func (self Mvhd) CC4() string { + return "mvhd" +} + +func (self Mvhd) Read(r Reader) (err error) { + return +} + +type Tkhd struct { +} + +func (self Tkhd) CC4() string { + return "tkhd" +} + +func (self *Tkhd) Read(r Reader) (err error) { + return +} + +type Minf struct { +} + +type Mdia struct { + *Minf +} + +type Trak struct { + Tkhd []*Tkhd + *Mdia +} + +func (self Trak) CC4() string { + return "tkhd" +} + +func (self *Trak) Read(r Reader) (err error) { + return +} + +// Time-to-Sample Atoms +type Stts struct { +} + +// Composition Offset Atom +type Ctts struct { +} + +// Sync Sample Atoms (Keyframe) +type Stss struct { +} + +// Sample-to-Chunk Atoms +type Stsc struct { +} + +// Sample Size Atoms +type Stsz struct { +} + +// Chunk Offset Atoms +type Stco struct { +} + diff --git a/atom/genAtomStruct.js b/atom/genAtomStruct.js new file mode 100644 index 0000000..4431b5b --- /dev/null +++ b/atom/genAtomStruct.js @@ -0,0 +1,294 @@ + +var ucfirst = x => x.substr(0,1).toUpperCase() + x.slice(1); + +var D = (cls, prop, ...fields) => { + var ctor = function (args) { + this.cls = cls; + Object.assign(this, prop); + fields.forEach((f, i) => this[f] = typeof(args[i]) == 'string' ? ucfirst(args[i]) : args[i]); + if (cls == 'Atom' || cls == 'AtomPtr') + this.type = this.type + 'Atom'; + }; + global[cls] = (...args) => new ctor(args); +} + +D('Int', {basic: true, type: 'int'}, 'name', 'len'); +D('Str', {basic: true, type: 'string'}, 'name', 'len'); +D('TimeStamp', {basic: true, type: 'TimeStamp'}, 'name', 'len'); +D('Bytes', {basic: true, type: '[]byte'}, 'name', 'len'); +D('Fixed32', {basic: true, type: 'Fixed32'}, 'name', 'len'); + +D('Atom', {}, 'type', 'name'); +D('AtomPtr', {}, 'type', 'name'); +D('Struct', {}, 'type', 'name'); +D('StructPtr', {}, 'type', 'name'); + +D('Arr', {}, 'name', 'elem', 'count'); +D('LenArr', {}, 'len', 'name', 'elem', 'isptr'); + +D('Size', {hide: true}, 'len'); +D('_', {type: 'Dummy', hide: true}, 'len'); + +var atoms = { + fileType: [ + 'ftyp', + AtomPtr('movie', 'movie'), + ], + + movie: [ + 'moov', + AtomPtr('movieHeader', 'header'), + Arr('tracks', AtomPtr('track')), + ], + + movieHeader: [ + 'mvhd', + Int('version', 1), + Int('flags', 3), + TimeStamp('cTime', 4), + TimeStamp('mTime', 4), + Int('timeScale', 4), + Int('duration', 4), + Int('preferredRate', 4), + Int('preferredVolume', 2), + _(10), + Bytes('matrix', 36), + TimeStamp('previewTime', 4), + TimeStamp('previewDuration', 4), + TimeStamp('posterTime', 4), + TimeStamp('selectionTime', 4), + TimeStamp('selectionDuration', 4), + TimeStamp('currentTime', 4), + Int('nextTrackId', 4), + ], + + track: [ + 'trak', + AtomPtr('trackHeader', 'header'), + AtomPtr('media', 'media'), + ], + + trackHeader: [ + 'tkhd', + Int('version', 1), + Int('flags', 3), + TimeStamp('cTime', 4), + TimeStamp('mTime', 4), + Int('trackId', 4), + _(4), + Int('duration', 4), + _(8), + Int('layer', 2), + Int('alternateGroup', 2), + Int('volume', 2), + _(2), + Bytes('matrix', 36), + Fixed32('trackWidth', 4), + Fixed32('trackHeight', 4), + ], + + media: [ + 'mdia', + AtomPtr('mediaHeader', 'header'), + AtomPtr('mediaInfo', 'info'), + ], + + mediaHeader: [ + 'mdhd', + Int('version', 1), + Int('flags', 3), + TimeStamp('cTime', 4), + TimeStamp('mTime', 4), + Int('timeScale', 4), + Int('duration', 4), + Int('language', 2), + Int('quality', 2), + ], + + mediaInfo: [ + 'minf', + AtomPtr('videoMediaInfo', 'video'), + AtomPtr('sampleTable', 'sample'), + ], + + videoMediaInfo: [ + 'vmhd', + Int('version', 1), + Int('flags', 3), + Int('graphicsMode', 2), + Arr('opcolor', Int('', 2), 3), + ], + + sampleTable: [ + 'stbl', + AtomPtr('sampleDesc', 'sampleDesc'), + AtomPtr('timeToSample', 'timeToSample'), + AtomPtr('compositionOffset', 'compositionOffset'), + AtomPtr('syncSample', 'syncSample'), + AtomPtr('sampleSize', 'sampleSize'), + AtomPtr('chunkOffset', 'chunkOffset'), + ], + + sampleDesc: [ + 'stsd', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Struct('sampleDescEntry')), + ], + + timeToSample: [ + 'stts', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Struct('timeToSampleEntry')), + ], + + compositionOffset: [ + 'ctts', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Struct('compositionOffsetEntry')), + ], + + syncSample: [ + 'stss', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Int('', 4)), + ], + + sampleSize: [ + 'stsz', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Int('', 4)), + ], + + chunkOffset: [ + 'stco', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Int('', 4)), + ], +}; + +var structs = { + sampleDescEntry: [ + Size(4), + Str('format', 4), + _(6), + Int('dataRefIdx', 2), + Bytes('data'), + ], + + timeToSampleEntry: [ + Int('count', 4), + Int('duration', 4), + ], + + compositionOffsetEntry: [ + Int('count', 4), + Int('offset', 4), + ], +}; + +var typeStr = field => ( + field.cls == 'AtomPtr' || field.cls == 'StructPtr') ? '*'+field.type : field.type; + +var dumpStruct = (name, list) => { + console.log(`type ${name} struct { + %s + }`, list + .filter(field => !field.hide) + .map(field => { + if (field.cls == 'Arr' || field.cls == 'LenArr') + return field.name +' []'+typeStr(field.elem); + return field.name+' '+typeStr(field) + }).join('\n') + ); + + dumpReadFn(name, null, false, list); +}; + +var dumpReadFn = (type, cc4, retptr, list) => { + var useSize; + + if (retptr) { + console.log(`func Read${type}(r io.LimitedReader) (res *${type}, err error) {`); + console.log(`self := &${type}{}`); + } else { + console.log(`func Read${type}(r io.LimitedReader) (self ${type}, err error) {`); + } + + list.forEach(field => { + if (field.cls == 'Size') { + useSize = true; + console.log(`size := ReadInt(r, ${field.len})`); + } else if (field.cls == 'Arr') { + var cond = field.count ? `i := 0; i < ${field.count}; i++` : `r.N > 0`; + console.log(`for ${cond} {`); + console.log(`var item ${typeStr(field.elem)}`); + console.log(`if item, err = Read${field.elem.type}(r); err != nil { + return + } else { + self.${field.name} = append(self.${field.name}, item) + }`); + console.log(`}`); + } else if (field.cls == 'LenArr') { + console.log(`if n, err := ReadInt(r, ${field.len}); err != nil { + return nil, err + } else { + for i := 0; i < n; i++ { + self.${field.name} = append(self.${field.name}, item) + } + }`); + } else { + var fn = field.basic ? field.cls : field.type; + var args = field.basic ? 'r, ' + field.len : 'r'; + console.log(`if self.${field.name}, err = Read${fn}(${args}); err != nil { + return + }`); + } + }); + + if (retptr) { + console.log(`res = self`); + } + console.log(`return`); + console.log(`}`); +}; + +var dumpWriteFn = (name, list) => { + var useSize; + + console.log(`func Write${name}(w Writer, atom ${name}) (err error) {`); + + list.map(x => { + if (x.type == 'size') { + useSize = true; + return `ReadInt(r, ${x.len})`; + } + }); + + console.log(`}`); +}; + +console.log('// THIS FILE IS AUTO GENERATED'); +console.log(''); +console.log('package atom'); + +for (var k in atoms) { + var list = atoms[k]; + var name = ucfirst(k)+'Atom'; + + var cc4 = list[0]; + list = list.slice(1); + dumpStruct(name, list); + dumpReadFn(name, cc4, true, list); +} + +for (var k in structs) { + var list = structs[k]; + dumpStruct(ucfirst(k), list) +} + diff --git a/atom/reader.go b/atom/reader.go new file mode 100644 index 0000000..188fd9f --- /dev/null +++ b/atom/reader.go @@ -0,0 +1,86 @@ + +package atom + +import ( + "io" + "io/ioutil" +) + +type Reader struct { + io.LimitedReader +} + +type Fixed32 uint32 + +func (self Reader) ReadUInt(n int) (res uint, err error) { + b := make([]byte, n) + if n, err = self.Read(b); err != nil { + return + } + for i := 0; i < n; i++ { + res <<= 8 + res += uint(b[i]) + } + return +} + +func (self Reader) ReadInt(n int) (res int, err error) { + var resu uint + if resu, err = self.ReadUInt(n); err != nil { + return + } + res = int(resu) + return +} + +func (self Reader) ReadString(n int) (res string, err error) { + b := make([]byte, n) + if n, err = self.Read(b); err != nil { + return + } + res = string(b) + return +} + +func (self Reader) Skip(n int) (err error) { + _, err = io.CopyN(ioutil.Discard, self.Reader, int64(n)) + return +} + +func (self Reader) ReadAtom(atom Atom) (res Atom, err error) { + for { + var size int + if size, err = self.ReadInt(4); err != nil { + return + } + if size == 0 { + continue + } + + var cc4 string + if cc4, err = self.ReadString(4); err != nil { + return + } + if atom.CC4() != cc4 { + if err = self.Skip(size); err != nil { + return + } + continue + } + + reader := &io.LimitedReader{ + R: self.Reader, + N: int64(size - 8), + } + if err = atom.Read(Reader{reader}); err != nil { + return + } + if err = self.Skip(int(reader.N)); err != nil { + return + } + + res = atom + return + } +} + diff --git a/atom/struct.go b/atom/struct.go new file mode 100644 index 0000000..9a3fac8 --- /dev/null +++ b/atom/struct.go @@ -0,0 +1,134 @@ +package atom + +type FileTypeAtom struct { + Movie *Movie +} + +type MovieAtom struct { + MovieHeader *MovieHeader +} + +type MovieHeaderAtom struct { + Version int + Flags int + CTime Ts + MTime Ts + TimeScale int + Duration int + PreferredRate int + PreferredVolume int + + Matrix []byte + PreviewTime Ts + PreviewDuration Ts + PosterTime Ts + SelectionTime Ts + SelectionDuration Ts + CurrentTime Ts + NextTrackId int +} + +type TrackAtom struct { + TrackHeader *TrackHeader + Media *Media +} + +type TrackHeaderAtom struct { + Version int + Flags int + CTime Ts + MTime Ts + TrackId int + + Duration int + + Layer int + AlternateGroup int + Volume int + + Matrix []byte + TrackWidth fixed32 + TrackHeight fixed32 +} + +type MediaAtom struct { + MediaHeader *MediaHeader + MediaInfo *MediaInfo +} + +type MediaHeaderAtom struct { + Version int + Flags int + CTime Ts + MTime Ts + TimeScale int + Duration int + Language int + Quality int +} + +type MediaInfoAtom struct { + VideoMediaInfo *VideoMediaInfo +} + +type VideoMediaInfoAtom struct { + Version int + Flags int + GraphicsMode int +} + +type SampleTableAtom struct { + SampleDesc *SampleDesc + TimeToSample *TimeToSample + CompositionOffset *CompositionOffset + SyncSample *SyncSample + SampleSize *SampleSize + ChunkOffset *ChunkOffset +} + +type SampleDescAtom struct { + Version int + Flags int +} + +type TimeToSampleAtom struct { + Version int + Flags int +} + +type CompositionOffsetAtom struct { + Version int + Flags int +} + +type SyncSampleAtom struct { + Version int + Flags int +} + +type SampleSizeAtom struct { + Version int + Flags int +} + +type ChunkOffsetAtom struct { + Version int + Flags int +} + +type sampleDescEntry struct { + Format string + + DataRefIdx int + Data []byte +} + +type timeToSampleEntry struct { + Count int + Duration int +} + +type compositionOffsetEntry struct { + Count int + Offset int +} diff --git a/avcc.go b/avcc.go new file mode 100644 index 0000000..b7d1592 --- /dev/null +++ b/avcc.go @@ -0,0 +1,17 @@ + +package mp4 + +type Avcc struct { + Config []byte + W, H int + Fps int +} + +// [dur][dur][dur][dur] + +// Duration() dur +// FindKeyFrame(at) at, dur +// Read(at) at, dur, buf +// Write(at, buf, dur) at +// RemoveAll() + diff --git a/mp4.go b/mp4.go new file mode 100644 index 0000000..347c4ca --- /dev/null +++ b/mp4.go @@ -0,0 +1,60 @@ + +package mp4 + +import ( + "./atom" + "os" +) + +type File struct { + moov *atom.Moov + ftyp *atom.Ftyp +} + +func (self *File) AddAvcc(avcc *Avcc) { +} + +func (self *File) AddMp4a(mp4a *Mp4a) { +} + +func (self *File) GetAvcc() (avcc []*Avcc) { + return +} + +func (self *File) GetMp4a() (mp4a []*Mp4a) { + return +} + +func (self *File) Sync() { +} + +func (self *File) Close() { +} + +func Open(filename string) (file *File, err error) { + var osfile *os.File + if osfile, err = os.Open(filename); err != nil { + return + } + + var entry atom.Atom + file = &File{} + r := atom.Reader{osfile} + + if entry, err = r.ReadAtom(&atom.Ftyp{}); err != nil { + return + } + file.ftyp = entry.(*atom.Ftyp) + + if entry, err = r.ReadAtom(&atom.Moov{}); err != nil { + return + } + file.moov = entry.(*atom.Moov) + + return +} + +func Create(filename string) (file *File, err error) { + return +} + diff --git a/mp4a.go b/mp4a.go new file mode 100644 index 0000000..614a430 --- /dev/null +++ b/mp4a.go @@ -0,0 +1,15 @@ + +package mp4 + +type Mp4a struct { + Config []byte + SampleRate int + Channels int +} + +// [dur][dur][dur][dur] + +// Read() +// Write(i, buf, dur) +// RemoveAll() + From dff1b98e9ddadeeabef360a3fda630ad0c916eff Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 19 Nov 2015 18:26:32 +0800 Subject: [PATCH 02/93] fix struct read funcs compile error --- atom/atom.go | 113 ---------- atom/genAtomStruct.js | 294 ------------------------ atom/genStruct.js | 354 +++++++++++++++++++++++++++++ atom/reader.go | 59 +++-- atom/struct.go | 503 ++++++++++++++++++++++++++++++++++++++---- 5 files changed, 856 insertions(+), 467 deletions(-) delete mode 100644 atom/genAtomStruct.js create mode 100644 atom/genStruct.js diff --git a/atom/atom.go b/atom/atom.go index 8cf5f58..b97bac0 100644 --- a/atom/atom.go +++ b/atom/atom.go @@ -1,116 +1,3 @@ package atom -import ( - "log" -) - -type Atom interface { - CC4() string - Read(Reader) error -} - -type Ftyp struct { -} - -func (self Ftyp) CC4() string { - return "ftyp" -} - -func (self Ftyp) Read(r Reader) (err error) { - log.Println("read ftyp") - return -} - -type Moov struct { - *Mvhd - Trak []*Trak -} - -func (self Moov) CC4() string { - return "moov" -} - -func (self *Moov) Read(r Reader) (err error) { - var atom Atom - if atom, err = r.ReadAtom(&Mvhd{}); err != nil { - return - } - self.Mvhd = atom.(*Mvhd) - - for { - if atom, err := r.ReadAtom(&Trak{}); err != nil { - break - } else { - self.Trak = append(self.Trak, atom.(*Trak)) - } - } - return -} - -type Mvhd struct { -} - -func (self Mvhd) CC4() string { - return "mvhd" -} - -func (self Mvhd) Read(r Reader) (err error) { - return -} - -type Tkhd struct { -} - -func (self Tkhd) CC4() string { - return "tkhd" -} - -func (self *Tkhd) Read(r Reader) (err error) { - return -} - -type Minf struct { -} - -type Mdia struct { - *Minf -} - -type Trak struct { - Tkhd []*Tkhd - *Mdia -} - -func (self Trak) CC4() string { - return "tkhd" -} - -func (self *Trak) Read(r Reader) (err error) { - return -} - -// Time-to-Sample Atoms -type Stts struct { -} - -// Composition Offset Atom -type Ctts struct { -} - -// Sync Sample Atoms (Keyframe) -type Stss struct { -} - -// Sample-to-Chunk Atoms -type Stsc struct { -} - -// Sample Size Atoms -type Stsz struct { -} - -// Chunk Offset Atoms -type Stco struct { -} - diff --git a/atom/genAtomStruct.js b/atom/genAtomStruct.js deleted file mode 100644 index 4431b5b..0000000 --- a/atom/genAtomStruct.js +++ /dev/null @@ -1,294 +0,0 @@ - -var ucfirst = x => x.substr(0,1).toUpperCase() + x.slice(1); - -var D = (cls, prop, ...fields) => { - var ctor = function (args) { - this.cls = cls; - Object.assign(this, prop); - fields.forEach((f, i) => this[f] = typeof(args[i]) == 'string' ? ucfirst(args[i]) : args[i]); - if (cls == 'Atom' || cls == 'AtomPtr') - this.type = this.type + 'Atom'; - }; - global[cls] = (...args) => new ctor(args); -} - -D('Int', {basic: true, type: 'int'}, 'name', 'len'); -D('Str', {basic: true, type: 'string'}, 'name', 'len'); -D('TimeStamp', {basic: true, type: 'TimeStamp'}, 'name', 'len'); -D('Bytes', {basic: true, type: '[]byte'}, 'name', 'len'); -D('Fixed32', {basic: true, type: 'Fixed32'}, 'name', 'len'); - -D('Atom', {}, 'type', 'name'); -D('AtomPtr', {}, 'type', 'name'); -D('Struct', {}, 'type', 'name'); -D('StructPtr', {}, 'type', 'name'); - -D('Arr', {}, 'name', 'elem', 'count'); -D('LenArr', {}, 'len', 'name', 'elem', 'isptr'); - -D('Size', {hide: true}, 'len'); -D('_', {type: 'Dummy', hide: true}, 'len'); - -var atoms = { - fileType: [ - 'ftyp', - AtomPtr('movie', 'movie'), - ], - - movie: [ - 'moov', - AtomPtr('movieHeader', 'header'), - Arr('tracks', AtomPtr('track')), - ], - - movieHeader: [ - 'mvhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('timeScale', 4), - Int('duration', 4), - Int('preferredRate', 4), - Int('preferredVolume', 2), - _(10), - Bytes('matrix', 36), - TimeStamp('previewTime', 4), - TimeStamp('previewDuration', 4), - TimeStamp('posterTime', 4), - TimeStamp('selectionTime', 4), - TimeStamp('selectionDuration', 4), - TimeStamp('currentTime', 4), - Int('nextTrackId', 4), - ], - - track: [ - 'trak', - AtomPtr('trackHeader', 'header'), - AtomPtr('media', 'media'), - ], - - trackHeader: [ - 'tkhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('trackId', 4), - _(4), - Int('duration', 4), - _(8), - Int('layer', 2), - Int('alternateGroup', 2), - Int('volume', 2), - _(2), - Bytes('matrix', 36), - Fixed32('trackWidth', 4), - Fixed32('trackHeight', 4), - ], - - media: [ - 'mdia', - AtomPtr('mediaHeader', 'header'), - AtomPtr('mediaInfo', 'info'), - ], - - mediaHeader: [ - 'mdhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('timeScale', 4), - Int('duration', 4), - Int('language', 2), - Int('quality', 2), - ], - - mediaInfo: [ - 'minf', - AtomPtr('videoMediaInfo', 'video'), - AtomPtr('sampleTable', 'sample'), - ], - - videoMediaInfo: [ - 'vmhd', - Int('version', 1), - Int('flags', 3), - Int('graphicsMode', 2), - Arr('opcolor', Int('', 2), 3), - ], - - sampleTable: [ - 'stbl', - AtomPtr('sampleDesc', 'sampleDesc'), - AtomPtr('timeToSample', 'timeToSample'), - AtomPtr('compositionOffset', 'compositionOffset'), - AtomPtr('syncSample', 'syncSample'), - AtomPtr('sampleSize', 'sampleSize'), - AtomPtr('chunkOffset', 'chunkOffset'), - ], - - sampleDesc: [ - 'stsd', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('sampleDescEntry')), - ], - - timeToSample: [ - 'stts', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('timeToSampleEntry')), - ], - - compositionOffset: [ - 'ctts', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('compositionOffsetEntry')), - ], - - syncSample: [ - 'stss', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], - - sampleSize: [ - 'stsz', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], - - chunkOffset: [ - 'stco', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], -}; - -var structs = { - sampleDescEntry: [ - Size(4), - Str('format', 4), - _(6), - Int('dataRefIdx', 2), - Bytes('data'), - ], - - timeToSampleEntry: [ - Int('count', 4), - Int('duration', 4), - ], - - compositionOffsetEntry: [ - Int('count', 4), - Int('offset', 4), - ], -}; - -var typeStr = field => ( - field.cls == 'AtomPtr' || field.cls == 'StructPtr') ? '*'+field.type : field.type; - -var dumpStruct = (name, list) => { - console.log(`type ${name} struct { - %s - }`, list - .filter(field => !field.hide) - .map(field => { - if (field.cls == 'Arr' || field.cls == 'LenArr') - return field.name +' []'+typeStr(field.elem); - return field.name+' '+typeStr(field) - }).join('\n') - ); - - dumpReadFn(name, null, false, list); -}; - -var dumpReadFn = (type, cc4, retptr, list) => { - var useSize; - - if (retptr) { - console.log(`func Read${type}(r io.LimitedReader) (res *${type}, err error) {`); - console.log(`self := &${type}{}`); - } else { - console.log(`func Read${type}(r io.LimitedReader) (self ${type}, err error) {`); - } - - list.forEach(field => { - if (field.cls == 'Size') { - useSize = true; - console.log(`size := ReadInt(r, ${field.len})`); - } else if (field.cls == 'Arr') { - var cond = field.count ? `i := 0; i < ${field.count}; i++` : `r.N > 0`; - console.log(`for ${cond} {`); - console.log(`var item ${typeStr(field.elem)}`); - console.log(`if item, err = Read${field.elem.type}(r); err != nil { - return - } else { - self.${field.name} = append(self.${field.name}, item) - }`); - console.log(`}`); - } else if (field.cls == 'LenArr') { - console.log(`if n, err := ReadInt(r, ${field.len}); err != nil { - return nil, err - } else { - for i := 0; i < n; i++ { - self.${field.name} = append(self.${field.name}, item) - } - }`); - } else { - var fn = field.basic ? field.cls : field.type; - var args = field.basic ? 'r, ' + field.len : 'r'; - console.log(`if self.${field.name}, err = Read${fn}(${args}); err != nil { - return - }`); - } - }); - - if (retptr) { - console.log(`res = self`); - } - console.log(`return`); - console.log(`}`); -}; - -var dumpWriteFn = (name, list) => { - var useSize; - - console.log(`func Write${name}(w Writer, atom ${name}) (err error) {`); - - list.map(x => { - if (x.type == 'size') { - useSize = true; - return `ReadInt(r, ${x.len})`; - } - }); - - console.log(`}`); -}; - -console.log('// THIS FILE IS AUTO GENERATED'); -console.log(''); -console.log('package atom'); - -for (var k in atoms) { - var list = atoms[k]; - var name = ucfirst(k)+'Atom'; - - var cc4 = list[0]; - list = list.slice(1); - dumpStruct(name, list); - dumpReadFn(name, cc4, true, list); -} - -for (var k in structs) { - var list = structs[k]; - dumpStruct(ucfirst(k), list) -} - diff --git a/atom/genStruct.js b/atom/genStruct.js new file mode 100644 index 0000000..94b2fa2 --- /dev/null +++ b/atom/genStruct.js @@ -0,0 +1,354 @@ + +var uc = x => x && x.substr(0,1).toUpperCase()+x.slice(1); +Array.prototype.nonull = function () { + return this.filter(x => x); +}; + +var Int = (name,len) => {return {name:uc(name),len,type:'int',fn:'Int'}}; +var Str = (name,len) => {return {name:uc(name),len,type:'string',fn:'String'}}; +var TimeStamp = (name,len) => {return {name:uc(name),len,type:'TimeStamp',fn:'TimeStamp'}}; +var Bytes = (name,len) => {return {name:uc(name),len,type:'[]byte',fn:'Bytes'}}; +var BytesLeft = (name) => {return {name:uc(name),type:'[]byte',fn:'BytesLeft'}}; +var Fixed32 = (name,len) => {return {name:uc(name),len,type:'Fixed32',fn:'Fixed32'}}; + +var Atom = (type,name) => {return {name:uc(name),type:uc(type)+'Atom',fn:uc(type)+'Atom'}}; +var AtomPtr = (type,name) => {return {name:uc(name),type:'*'+uc(type)+'Atom',fn:uc(type)+'Atom'}}; + +var Struct = (type,name) => {return {name:uc(name),type:uc(type),fn:uc(type)}}; +var StructPtr = (type,name) => {return {name:uc(name),type:'*'+uc(type),fn:uc(type)}}; + +var Arr = (name,elem,count) => {return {name:uc(name),elem,count,type:'[]'+elem.type}}; +var LenArr = (sizelen,name,elem) => {return {sizelen,name:uc(name),elem,type:'[]'+elem.type}}; + +var Size = (len) => {return {len,hide:true,fn:'Int'}}; +var _ = (len) => {return {len,hide:true,fn:'Dummy'}}; + +var atoms = { + fileType: [ + 'ftyp', + AtomPtr('movie', 'movie'), + ], + + movie: [ + 'moov', + AtomPtr('movieHeader', 'header'), + Arr('tracks', AtomPtr('track')), + ], + + movieHeader: [ + 'mvhd', + Int('version', 1), + Int('flags', 3), + TimeStamp('cTime', 4), + TimeStamp('mTime', 4), + Int('timeScale', 4), + Int('duration', 4), + Int('preferredRate', 4), + Int('preferredVolume', 2), + _(10), + Bytes('matrix', 36), + TimeStamp('previewTime', 4), + TimeStamp('previewDuration', 4), + TimeStamp('posterTime', 4), + TimeStamp('selectionTime', 4), + TimeStamp('selectionDuration', 4), + TimeStamp('currentTime', 4), + Int('nextTrackId', 4), + ], + + track: [ + 'trak', + AtomPtr('trackHeader', 'header'), + AtomPtr('media', 'media'), + ], + + trackHeader: [ + 'tkhd', + Int('version', 1), + Int('flags', 3), + TimeStamp('cTime', 4), + TimeStamp('mTime', 4), + Int('trackId', 4), + _(4), + Int('duration', 4), + _(8), + Int('layer', 2), + Int('alternateGroup', 2), + Int('volume', 2), + _(2), + Bytes('matrix', 36), + Fixed32('trackWidth', 4), + Fixed32('trackHeight', 4), + ], + + media: [ + 'mdia', + AtomPtr('mediaHeader', 'header'), + AtomPtr('mediaInfo', 'info'), + ], + + mediaHeader: [ + 'mdhd', + Int('version', 1), + Int('flags', 3), + TimeStamp('cTime', 4), + TimeStamp('mTime', 4), + Int('timeScale', 4), + Int('duration', 4), + Int('language', 2), + Int('quality', 2), + ], + + mediaInfo: [ + 'minf', + AtomPtr('videoMediaInfo', 'video'), + AtomPtr('sampleTable', 'sample'), + ], + + videoMediaInfo: [ + 'vmhd', + Int('version', 1), + Int('flags', 3), + Int('graphicsMode', 2), + Arr('opcolor', Int('', 2), 3), + ], + + sampleTable: [ + 'stbl', + AtomPtr('sampleDesc', 'sampleDesc'), + AtomPtr('timeToSample', 'timeToSample'), + AtomPtr('compositionOffset', 'compositionOffset'), + AtomPtr('syncSample', 'syncSample'), + AtomPtr('sampleSize', 'sampleSize'), + AtomPtr('chunkOffset', 'chunkOffset'), + ], + + sampleDesc: [ + 'stsd', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Struct('sampleDescEntry')), + ], + + timeToSample: [ + 'stts', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Struct('timeToSampleEntry')), + ], + + compositionOffset: [ + 'ctts', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Struct('compositionOffsetEntry')), + ], + + syncSample: [ + 'stss', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Int('', 4)), + ], + + sampleSize: [ + 'stsz', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Int('', 4)), + ], + + chunkOffset: [ + 'stco', + Int('version', 1), + Int('flags', 3), + LenArr(4, 'entries', Int('', 4)), + ], +}; + +var structs = { + sampleDescEntry: [ + Size(4), + Str('format', 4), + _(6), + Int('dataRefIdx', 2), + BytesLeft('data'), + ], + + timeToSampleEntry: [ + Int('count', 4), + Int('duration', 4), + ], + + compositionOffsetEntry: [ + Int('count', 4), + Int('offset', 4), + ], +}; + +var genReadStmts = (opts) => { + var stmts = []; + + if (opts.resIsPtr) + stmts = stmts.concat([StrStmt(`self := &${opts.atomType}{}`)]); + + var readElemStmts = field => { + var arr = 'self.'+field.name; + return [ + DeclVar('item', field.elem.type), + CallCheckAssign('Read'+field.elem.fn, ['r', field.elem.len].nonull(), ['item']), + StrStmt(`${arr} = append(${arr}, item)`), + ] + }; + + stmts = stmts.concat(opts.fields.map(field => { + if (field.sizelen) { + var arr = 'self.'+field.name; + return [ + DeclVar('count', 'int'), + CallCheckAssign('ReadInt', ['r', field.sizelen], ['count']), + For(RangeN('i', 'count'), readElemStmts(field)), + ]; + } else if (field.elem) { + var cond = field.count ? RangeN('i', field.count) : StrStmt('r.N > 0'); + return For(cond, readElemStmts(field)); + } else if (!field.hide) { + return CallCheckAssign('Read'+field.fn, ['r', field.len].nonull(), ['self.'+field.name]); + } + }).nonull()); + + if (opts.resIsPtr) + stmts = stmts.concat([StrStmt(`res = self`)]); + + return Func( + 'Read'+opts.fnName, + [['r', '*io.LimitedReader']], + [[opts.resIsPtr?'res':'self', (opts.resIsPtr?'*':'')+opts.atomType], ['err', 'error']], + stmts + ); +}; + +var D = (cls, ...fields) => { + global[cls] = (...args) => { + var obj = {cls: cls}; + fields.forEach((k, i) => obj[k] = args[i]); + return obj; + }; +}; + +D('Func', 'name', 'args', 'rets', 'body'); +D('CallCheckAssign', 'fn', 'args', 'rets'); +D('DeclVar', 'name', 'type'); +D('For', 'cond', 'body'); +D('RangeN', 'i', 'n'); +D('DeclStruct', 'name', 'body'); +D('StrStmt', 'content'); + +var dumpFn = f => { + var dumpArgs = x => x.map(x => x.join(' ')).join(','); + return `func ${f.name}(${dumpArgs(f.args)}) (${dumpArgs(f.rets)}) { + ${dumpStmts(f.body)} + return + }`; +}; + +var dumpStmts = stmts => { + var dumpStmt = stmt => { + if (stmt instanceof Array) { + return dumpStmts(stmt); + } if (stmt.cls == 'CallCheckAssign') { + return `if ${stmt.rets.concat(['err']).join(',')} = ${stmt.fn}(${stmt.args.join(',')}); err != nil { + return + }`; + } else if (stmt.cls == 'DeclVar') { + return `var ${stmt.name} ${stmt.type}`; + } else if (stmt.cls == 'For') { + return `for ${dumpStmt(stmt.cond)} { + ${dumpStmts(stmt.body)} + }`; + } else if (stmt.cls == 'RangeN') { + return `${stmt.i} := 0; ${stmt.i} < ${stmt.n}; ${stmt.i}++`; + } else if (stmt.cls == 'DeclStruct') { + return `type ${stmt.name} struct { + ${stmt.body.map(line => line.join(' ')).join('\n')} + }`; + } else if (stmt.cls == 'Func') { + return dumpFn(stmt); + } else if (stmt.cls == 'StrStmt') { + return stmt.content; + } + }; + return stmts.map(dumpStmt).join('\n') +}; + +(() => { + var len = 3; + var f = Func('Readxx', [['f', '*io.LimitedReader']], [['res', '*xx'], ['err', 'error']], [ + CallCheckAssign('ReadInt', ['f', len], ['self.xx']), + CallCheckAssign('WriteInt', ['f', len], ['self.xx']), + DeclVar('n', 'int'), + For(RangeN('i', 'n'), [ + CallCheckAssign('WriteInt', ['f', len], ['self.xx']), + DeclStruct('hi', [['a', 'b'], ['c', 'd'], ['e', 'f']]), + ]), + ]); + console.log(dumpFn(f)); +}); + +var allStmts = () => { + var stmts = []; + + var convStructFields = fields => { + var typeStr = field => ( + field.cls == 'AtomPtr' || field.cls == 'StructPtr') ? '*'+field.type : field.type; + + return fields.filter(field => !field.hide) + .map(field => { + if (field.cls == 'Arr' || field.cls == 'LenArr') + return [field.name, '[]'+typeStr(field.elem)]; + return [field.name, typeStr(field)]; + }); + }; + + for (var k in atoms) { + var list = atoms[k]; + var name = uc(k)+'Atom'; + var cc4 = list[0]; + var fields = list.slice(1); + + stmts = stmts.concat([ + DeclStruct(name, convStructFields(fields)), + genReadStmts({ + cc4: cc4, + fields: fields, + fnName: name, + atomType: name, + resIsPtr: true, + }), + ]); + } + + for (var k in structs) { + var fields = structs[k]; + var name = uc(k); + + stmts = stmts.concat([ + DeclStruct(name, convStructFields(fields)), + genReadStmts({ + fields: fields, + fnName: name, + atomType: name, + }), + ]); + } + + return stmts; +}; + +console.log(`// THIS FILE IS AUTO GENERATED +package atom +import ( + "io" +) +`, dumpStmts(allStmts())); + diff --git a/atom/reader.go b/atom/reader.go index 188fd9f..4af4a3a 100644 --- a/atom/reader.go +++ b/atom/reader.go @@ -6,15 +6,24 @@ import ( "io/ioutil" ) -type Reader struct { - io.LimitedReader +type Fixed32 uint32 +type TimeStamp uint32 + +func ReadBytes(r io.Reader, n int) (res []byte, err error) { + res = make([]byte, n) + if n, err = r.Read(res); err != nil { + return + } + return } -type Fixed32 uint32 +func ReadBytesLeft(r *io.LimitedReader) (res []byte, err error) { + return ReadBytes(r, int(r.N)) +} -func (self Reader) ReadUInt(n int) (res uint, err error) { - b := make([]byte, n) - if n, err = self.Read(b); err != nil { +func ReadUInt(r io.Reader, n int) (res uint, err error) { + var b []byte + if b, err = ReadBytes(r, n); err != nil { return } for i := 0; i < n; i++ { @@ -24,29 +33,48 @@ func (self Reader) ReadUInt(n int) (res uint, err error) { return } -func (self Reader) ReadInt(n int) (res int, err error) { - var resu uint - if resu, err = self.ReadUInt(n); err != nil { +func ReadInt(r io.Reader, n int) (res int, err error) { + var ui uint + if ui, err = ReadUInt(r, n); err != nil { return } - res = int(resu) + res = int(ui) return } -func (self Reader) ReadString(n int) (res string, err error) { - b := make([]byte, n) - if n, err = self.Read(b); err != nil { +func ReadFixed32(r io.Reader, n int) (res Fixed32, err error) { + var ui uint + if ui, err = ReadUInt(r, n); err != nil { + return + } + res = Fixed32(ui) + return +} + +func ReadTimeStamp(r io.Reader, n int) (res TimeStamp, err error) { + var ui uint + if ui, err = ReadUInt(r, n); err != nil { + return + } + res = TimeStamp(ui) + return +} + +func ReadString(r io.Reader, n int) (res string, err error) { + var b []byte + if b, err = ReadBytes(r, n); err != nil { return } res = string(b) return } -func (self Reader) Skip(n int) (err error) { - _, err = io.CopyN(ioutil.Discard, self.Reader, int64(n)) +func ReadDummy(r io.Reader, n int) (res int, err error) { + _, err = io.CopyN(ioutil.Discard, r, int64(n)) return } +/* func (self Reader) ReadAtom(atom Atom) (res Atom, err error) { for { var size int @@ -83,4 +111,5 @@ func (self Reader) ReadAtom(atom Atom) (res Atom, err error) { return } } +*/ diff --git a/atom/struct.go b/atom/struct.go index 9a3fac8..f0a240f 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -1,134 +1,547 @@ +// THIS FILE IS AUTO GENERATED package atom +import ( + "io" +) + type FileTypeAtom struct { - Movie *Movie + Movie *MovieAtom +} + +func ReadFileTypeAtom(r *io.LimitedReader) (res *FileTypeAtom, err error) { + self := &FileTypeAtom{} + if self.Movie, err = ReadMovieAtom(r); err != nil { + return + } + res = self + return } type MovieAtom struct { - MovieHeader *MovieHeader + Header *MovieHeaderAtom + Tracks []*TrackAtom +} + +func ReadMovieAtom(r *io.LimitedReader) (res *MovieAtom, err error) { + self := &MovieAtom{} + if self.Header, err = ReadMovieHeaderAtom(r); err != nil { + return + } + for r.N > 0 { + var item *TrackAtom + if item, err = ReadTrackAtom(r); err != nil { + return + } + self.Tracks = append(self.Tracks, item) + } + res = self + return } type MovieHeaderAtom struct { - Version int - Flags int - CTime Ts - MTime Ts - TimeScale int - Duration int - PreferredRate int - PreferredVolume int - + Version int + Flags int + CTime TimeStamp + MTime TimeStamp + TimeScale int + Duration int + PreferredRate int + PreferredVolume int Matrix []byte - PreviewTime Ts - PreviewDuration Ts - PosterTime Ts - SelectionTime Ts - SelectionDuration Ts - CurrentTime Ts + PreviewTime TimeStamp + PreviewDuration TimeStamp + PosterTime TimeStamp + SelectionTime TimeStamp + SelectionDuration TimeStamp + CurrentTime TimeStamp NextTrackId int } +func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) { + self := &MovieHeaderAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.CTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.MTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.TimeScale, err = ReadInt(r, 4); err != nil { + return + } + if self.Duration, err = ReadInt(r, 4); err != nil { + return + } + if self.PreferredRate, err = ReadInt(r, 4); err != nil { + return + } + if self.PreferredVolume, err = ReadInt(r, 2); err != nil { + return + } + if self.Matrix, err = ReadBytes(r, 36); err != nil { + return + } + if self.PreviewTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.PreviewDuration, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.PosterTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.SelectionTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.SelectionDuration, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.CurrentTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.NextTrackId, err = ReadInt(r, 4); err != nil { + return + } + res = self + return +} + type TrackAtom struct { - TrackHeader *TrackHeader - Media *Media + Header *TrackHeaderAtom + Media *MediaAtom +} + +func ReadTrackAtom(r *io.LimitedReader) (res *TrackAtom, err error) { + self := &TrackAtom{} + if self.Header, err = ReadTrackHeaderAtom(r); err != nil { + return + } + if self.Media, err = ReadMediaAtom(r); err != nil { + return + } + res = self + return } type TrackHeaderAtom struct { - Version int - Flags int - CTime Ts - MTime Ts - TrackId int - - Duration int - + Version int + Flags int + CTime TimeStamp + MTime TimeStamp + TrackId int + Duration int Layer int AlternateGroup int Volume int + Matrix []byte + TrackWidth Fixed32 + TrackHeight Fixed32 +} - Matrix []byte - TrackWidth fixed32 - TrackHeight fixed32 +func ReadTrackHeaderAtom(r *io.LimitedReader) (res *TrackHeaderAtom, err error) { + self := &TrackHeaderAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.CTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.MTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.TrackId, err = ReadInt(r, 4); err != nil { + return + } + if self.Duration, err = ReadInt(r, 4); err != nil { + return + } + if self.Layer, err = ReadInt(r, 2); err != nil { + return + } + if self.AlternateGroup, err = ReadInt(r, 2); err != nil { + return + } + if self.Volume, err = ReadInt(r, 2); err != nil { + return + } + if self.Matrix, err = ReadBytes(r, 36); err != nil { + return + } + if self.TrackWidth, err = ReadFixed32(r, 4); err != nil { + return + } + if self.TrackHeight, err = ReadFixed32(r, 4); err != nil { + return + } + res = self + return } type MediaAtom struct { - MediaHeader *MediaHeader - MediaInfo *MediaInfo + Header *MediaHeaderAtom + Info *MediaInfoAtom +} + +func ReadMediaAtom(r *io.LimitedReader) (res *MediaAtom, err error) { + self := &MediaAtom{} + if self.Header, err = ReadMediaHeaderAtom(r); err != nil { + return + } + if self.Info, err = ReadMediaInfoAtom(r); err != nil { + return + } + res = self + return } type MediaHeaderAtom struct { Version int Flags int - CTime Ts - MTime Ts + CTime TimeStamp + MTime TimeStamp TimeScale int Duration int Language int Quality int } +func ReadMediaHeaderAtom(r *io.LimitedReader) (res *MediaHeaderAtom, err error) { + self := &MediaHeaderAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.CTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.MTime, err = ReadTimeStamp(r, 4); err != nil { + return + } + if self.TimeScale, err = ReadInt(r, 4); err != nil { + return + } + if self.Duration, err = ReadInt(r, 4); err != nil { + return + } + if self.Language, err = ReadInt(r, 2); err != nil { + return + } + if self.Quality, err = ReadInt(r, 2); err != nil { + return + } + res = self + return +} + type MediaInfoAtom struct { - VideoMediaInfo *VideoMediaInfo + Video *VideoMediaInfoAtom + Sample *SampleTableAtom +} + +func ReadMediaInfoAtom(r *io.LimitedReader) (res *MediaInfoAtom, err error) { + self := &MediaInfoAtom{} + if self.Video, err = ReadVideoMediaInfoAtom(r); err != nil { + return + } + if self.Sample, err = ReadSampleTableAtom(r); err != nil { + return + } + res = self + return } type VideoMediaInfoAtom struct { Version int Flags int GraphicsMode int + Opcolor []int +} + +func ReadVideoMediaInfoAtom(r *io.LimitedReader) (res *VideoMediaInfoAtom, err error) { + self := &VideoMediaInfoAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.GraphicsMode, err = ReadInt(r, 2); err != nil { + return + } + for i := 0; i < 3; i++ { + var item int + if item, err = ReadInt(r, 2); err != nil { + return + } + self.Opcolor = append(self.Opcolor, item) + } + res = self + return } type SampleTableAtom struct { - SampleDesc *SampleDesc - TimeToSample *TimeToSample - CompositionOffset *CompositionOffset - SyncSample *SyncSample - SampleSize *SampleSize - ChunkOffset *ChunkOffset + SampleDesc *SampleDescAtom + TimeToSample *TimeToSampleAtom + CompositionOffset *CompositionOffsetAtom + SyncSample *SyncSampleAtom + SampleSize *SampleSizeAtom + ChunkOffset *ChunkOffsetAtom +} + +func ReadSampleTableAtom(r *io.LimitedReader) (res *SampleTableAtom, err error) { + self := &SampleTableAtom{} + if self.SampleDesc, err = ReadSampleDescAtom(r); err != nil { + return + } + if self.TimeToSample, err = ReadTimeToSampleAtom(r); err != nil { + return + } + if self.CompositionOffset, err = ReadCompositionOffsetAtom(r); err != nil { + return + } + if self.SyncSample, err = ReadSyncSampleAtom(r); err != nil { + return + } + if self.SampleSize, err = ReadSampleSizeAtom(r); err != nil { + return + } + if self.ChunkOffset, err = ReadChunkOffsetAtom(r); err != nil { + return + } + res = self + return } type SampleDescAtom struct { Version int Flags int + Entries []SampleDescEntry +} + +func ReadSampleDescAtom(r *io.LimitedReader) (res *SampleDescAtom, err error) { + self := &SampleDescAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + for i := 0; i < count; i++ { + var item SampleDescEntry + if item, err = ReadSampleDescEntry(r); err != nil { + return + } + self.Entries = append(self.Entries, item) + } + res = self + return } type TimeToSampleAtom struct { Version int Flags int + Entries []TimeToSampleEntry +} + +func ReadTimeToSampleAtom(r *io.LimitedReader) (res *TimeToSampleAtom, err error) { + self := &TimeToSampleAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + for i := 0; i < count; i++ { + var item TimeToSampleEntry + if item, err = ReadTimeToSampleEntry(r); err != nil { + return + } + self.Entries = append(self.Entries, item) + } + res = self + return } type CompositionOffsetAtom struct { Version int Flags int + Entries []CompositionOffsetEntry +} + +func ReadCompositionOffsetAtom(r *io.LimitedReader) (res *CompositionOffsetAtom, err error) { + self := &CompositionOffsetAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + for i := 0; i < count; i++ { + var item CompositionOffsetEntry + if item, err = ReadCompositionOffsetEntry(r); err != nil { + return + } + self.Entries = append(self.Entries, item) + } + res = self + return } type SyncSampleAtom struct { Version int Flags int + Entries []int +} + +func ReadSyncSampleAtom(r *io.LimitedReader) (res *SyncSampleAtom, err error) { + self := &SyncSampleAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + for i := 0; i < count; i++ { + var item int + if item, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = append(self.Entries, item) + } + res = self + return } type SampleSizeAtom struct { Version int Flags int + Entries []int +} + +func ReadSampleSizeAtom(r *io.LimitedReader) (res *SampleSizeAtom, err error) { + self := &SampleSizeAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + for i := 0; i < count; i++ { + var item int + if item, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = append(self.Entries, item) + } + res = self + return } type ChunkOffsetAtom struct { Version int Flags int + Entries []int } -type sampleDescEntry struct { - Format string +func ReadChunkOffsetAtom(r *io.LimitedReader) (res *ChunkOffsetAtom, err error) { + self := &ChunkOffsetAtom{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + for i := 0; i < count; i++ { + var item int + if item, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = append(self.Entries, item) + } + res = self + return +} +type SampleDescEntry struct { + Format string DataRefIdx int Data []byte } -type timeToSampleEntry struct { +func ReadSampleDescEntry(r *io.LimitedReader) (self SampleDescEntry, err error) { + if self.Format, err = ReadString(r, 4); err != nil { + return + } + if self.DataRefIdx, err = ReadInt(r, 2); err != nil { + return + } + if self.Data, err = ReadBytesLeft(r); err != nil { + return + } + return +} + +type TimeToSampleEntry struct { Count int Duration int } -type compositionOffsetEntry struct { +func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err error) { + if self.Count, err = ReadInt(r, 4); err != nil { + return + } + if self.Duration, err = ReadInt(r, 4); err != nil { + return + } + return +} + +type CompositionOffsetEntry struct { Count int Offset int } + +func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntry, err error) { + if self.Count, err = ReadInt(r, 4); err != nil { + return + } + if self.Offset, err = ReadInt(r, 4); err != nil { + return + } + return +} From 3bbc27f4704fac50167eb93d489fdd1761aeae40 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 21 Nov 2015 14:09:02 +0800 Subject: [PATCH 03/93] rewrite genStruct.js --- atom/genStruct.js | 603 +++++++++++++++++++++++---------------- atom/reader.go | 39 ++- atom/struct.go | 712 +++++++++++++++++++++++++++++----------------- example/read.go | 15 + mp4.go | 35 ++- 5 files changed, 873 insertions(+), 531 deletions(-) create mode 100644 example/read.go diff --git a/atom/genStruct.js b/atom/genStruct.js index 94b2fa2..6c68689 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -4,227 +4,302 @@ Array.prototype.nonull = function () { return this.filter(x => x); }; -var Int = (name,len) => {return {name:uc(name),len,type:'int',fn:'Int'}}; -var Str = (name,len) => {return {name:uc(name),len,type:'string',fn:'String'}}; -var TimeStamp = (name,len) => {return {name:uc(name),len,type:'TimeStamp',fn:'TimeStamp'}}; -var Bytes = (name,len) => {return {name:uc(name),len,type:'[]byte',fn:'Bytes'}}; -var BytesLeft = (name) => {return {name:uc(name),type:'[]byte',fn:'BytesLeft'}}; -var Fixed32 = (name,len) => {return {name:uc(name),len,type:'Fixed32',fn:'Fixed32'}}; - -var Atom = (type,name) => {return {name:uc(name),type:uc(type)+'Atom',fn:uc(type)+'Atom'}}; -var AtomPtr = (type,name) => {return {name:uc(name),type:'*'+uc(type)+'Atom',fn:uc(type)+'Atom'}}; - -var Struct = (type,name) => {return {name:uc(name),type:uc(type),fn:uc(type)}}; -var StructPtr = (type,name) => {return {name:uc(name),type:'*'+uc(type),fn:uc(type)}}; - -var Arr = (name,elem,count) => {return {name:uc(name),elem,count,type:'[]'+elem.type}}; -var LenArr = (sizelen,name,elem) => {return {sizelen,name:uc(name),elem,type:'[]'+elem.type}}; - -var Size = (len) => {return {len,hide:true,fn:'Int'}}; -var _ = (len) => {return {len,hide:true,fn:'Dummy'}}; - var atoms = { - fileType: [ - 'ftyp', - AtomPtr('movie', 'movie'), - ], + fileType: { + cc4: 'ftyp', + fields: [ + ], + }, - movie: [ - 'moov', - AtomPtr('movieHeader', 'header'), - Arr('tracks', AtomPtr('track')), - ], + movie: { + cc4: 'moov', + atoms: [ + ['header', '*movieHeader'], + ['tracks', '[]*track'], + ], + }, - movieHeader: [ - 'mvhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('timeScale', 4), - Int('duration', 4), - Int('preferredRate', 4), - Int('preferredVolume', 2), - _(10), - Bytes('matrix', 36), - TimeStamp('previewTime', 4), - TimeStamp('previewDuration', 4), - TimeStamp('posterTime', 4), - TimeStamp('selectionTime', 4), - TimeStamp('selectionDuration', 4), - TimeStamp('currentTime', 4), - Int('nextTrackId', 4), - ], + movieHeader: { + cc4: 'mvhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['cTime', 'TimeStamp32'], + ['mTime', 'TimeStamp32'], + ['timeScale', 'TimeStamp32'], + ['duration', 'TimeStamp32'], + ['preferredRate', 'int32'], + ['preferredVolume', 'int16'], + ['_', '[10]byte'], + ['matrix', '[9]int32'], + ['previewTime', 'TimeStamp32'], + ['previewDuration', 'TimeStamp32'], + ['posterTime', 'TimeStamp32'], + ['selectionTime', 'TimeStamp32'], + ['selectionDuration', 'TimeStamp32'], + ['currentTime', 'TimeStamp32'], + ['nextTrackId', 'int32'], + ], + }, - track: [ - 'trak', - AtomPtr('trackHeader', 'header'), - AtomPtr('media', 'media'), - ], + track: { + cc4: 'trak', + atoms: [ + ['header', '*trackHeader'], + ['media', '*media'], + ], + }, - trackHeader: [ - 'tkhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('trackId', 4), - _(4), - Int('duration', 4), - _(8), - Int('layer', 2), - Int('alternateGroup', 2), - Int('volume', 2), - _(2), - Bytes('matrix', 36), - Fixed32('trackWidth', 4), - Fixed32('trackHeight', 4), - ], + trackHeader: { + cc4: 'tkhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['cTime', 'TimeStamp32'], + ['mTime', 'TimeStamp32'], + ['trackId', 'TimeStamp32'], + ['_', '[4]byte'], + ['duration', 'TimeStamp32'], + ['_', '[8]byte'], + ['layer', 'int16'], + ['alternateGroup', 'int16'], + ['volume', 'int16'], + ['_', '[2]byte'], + ['matrix', '[9]int32'], + ['trackWidth', 'int32'], + ['trackHeader', 'int32'], + ], + }, - media: [ - 'mdia', - AtomPtr('mediaHeader', 'header'), - AtomPtr('mediaInfo', 'info'), - ], + media: { + cc4: 'mdia', + atoms: [ + ['header', '*mediaHeader'], + ['info', '*mediaInfo'], + ], + }, - mediaHeader: [ - 'mdhd', - Int('version', 1), - Int('flags', 3), - TimeStamp('cTime', 4), - TimeStamp('mTime', 4), - Int('timeScale', 4), - Int('duration', 4), - Int('language', 2), - Int('quality', 2), - ], + mediaHeader: { + cc4: 'mdhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['cTime', 'int32'], + ['mTime', 'int32'], + ['timeScale', 'int32'], + ['duration', 'int32'], + ['language', 'int16'], + ['quality', 'int16'], + ], + }, - mediaInfo: [ - 'minf', - AtomPtr('videoMediaInfo', 'video'), - AtomPtr('sampleTable', 'sample'), - ], + mediaInfo: { + cc4: 'minf', + atoms: [ + ['sound', '*soundMediaInfo'], + ['video', '*videoMediaInfo'], + ['sample', '*sampleTable'], + ], + }, - videoMediaInfo: [ - 'vmhd', - Int('version', 1), - Int('flags', 3), - Int('graphicsMode', 2), - Arr('opcolor', Int('', 2), 3), - ], + soundMediaInfo: { + cc4: 'smhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['balance', 'int16'], + ['_', 'int16'], + ], + }, - sampleTable: [ - 'stbl', - AtomPtr('sampleDesc', 'sampleDesc'), - AtomPtr('timeToSample', 'timeToSample'), - AtomPtr('compositionOffset', 'compositionOffset'), - AtomPtr('syncSample', 'syncSample'), - AtomPtr('sampleSize', 'sampleSize'), - AtomPtr('chunkOffset', 'chunkOffset'), - ], + videoMediaInfo: { + cc4: 'vmhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['graphicsMode', 'int16'], + ['opcolor', '[3]int16'], + ], + }, - sampleDesc: [ - 'stsd', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('sampleDescEntry')), - ], + sampleTable: { + cc4: 'stbl', + atoms: [ + ['sampleDesc', '*sampleDesc'], + ['timeToSample', '*timeToSample'], + ['compositionOffset', '*compositionOffset'], + ['sampleToChunk', '*sampleToChunk'], + ['syncSample', '*syncSample'], + ['chunkOffset', '*chunkOffset'], + ['sampleSize', '*sampleSize'], + ], + }, - timeToSample: [ - 'stts', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('timeToSampleEntry')), - ], + sampleDesc: { + cc4: 'stsd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]*sampleDescEntry'], + ], + }, - compositionOffset: [ - 'ctts', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Struct('compositionOffsetEntry')), - ], + timeToSample: { + cc4: 'stts', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]timeToSampleEntry'], + ], + }, - syncSample: [ - 'stss', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], + timeToSampleEntry: { + fields: [ + ['count', 'int32'], + ['duration', 'int32'], + ], + }, - sampleSize: [ - 'stsz', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], + sampleToChunk: { + cc4: 'stsc', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]sampleToChunkEntry'], + ], + }, + + sampleToChunkEntry: { + fields: [ + ['firstChunk', 'int32'], + ['samplesPerChunk', 'int32'], + ['sampleDescId', 'int32'], + ], + }, + + compositionOffset: { + cc4: 'ctts', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]int32'], + ], + }, + + compositionOffsetEntry: { + fields: [ + ['count', 'int32'], + ['offset', 'int32'], + ], + }, + + syncSample: { + cc4: 'stss', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]int32'], + ], + }, + + sampleSize: { + cc4: 'stsz', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]int32'], + ], + }, + + chunkOffset: { + cc4: 'stco', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['entries', '[int32]int32'], + ], + }, - chunkOffset: [ - 'stco', - Int('version', 1), - Int('flags', 3), - LenArr(4, 'entries', Int('', 4)), - ], }; -var structs = { - sampleDescEntry: [ - Size(4), - Str('format', 4), - _(6), - Int('dataRefIdx', 2), - BytesLeft('data'), - ], - - timeToSampleEntry: [ - Int('count', 4), - Int('duration', 4), - ], - - compositionOffsetEntry: [ - Int('count', 4), - Int('offset', 4), - ], -}; - -var genReadStmts = (opts) => { +var DeclReadFunc = (opts) => { var stmts = []; - if (opts.resIsPtr) - stmts = stmts.concat([StrStmt(`self := &${opts.atomType}{}`)]); + var DebugStmt = type => StrStmt(`// ${JSON.stringify(type)}`); - var readElemStmts = field => { - var arr = 'self.'+field.name; + var ReadArr = (name, type) => { return [ - DeclVar('item', field.elem.type), - CallCheckAssign('Read'+field.elem.fn, ['r', field.elem.len].nonull(), ['item']), - StrStmt(`${arr} = append(${arr}, item)`), + //StrStmt('// ReadArr'), + //DebugStmt(type), + type.varcount && [ + DeclVar('count', 'int'), + CallCheckAssign('ReadInt', ['r', type.varcount], ['count']), + StrStmt(`${name} = make(${typeStr(type)}, count)`), + ], + For(RangeN('i', type.varcount ? 'count' : type.count), [ + ReadCommnType(name+'[i]', type), + ]), + ]; + }; + + var elemTypeStr = type => typeStr(Object.assign({}, type, {arr: false})); + var ReadAtoms = () => [ + StrStmt(`// ReadAtoms`), + For(StrStmt(`r.N > 0`), [ + DeclVar('cc4', 'string'), + DeclVar('ar', '*io.LimitedReader'), + CallCheckAssign('ReadAtomHeader', ['r', '""'], ['ar', 'cc4']), + Switch('cc4', opts.fields.map(field => [ + `"${atoms[field.type.struct].cc4}"`, [ + field.type.arr ? [ + DeclVar('item', elemTypeStr(field.type)), + CallCheckAssign('Read'+field.type.Struct, ['ar'], ['item']), + StrStmt(`self.${field.name} = append(self.${field.name}, item)`), + ] : [ + CallCheckAssign('Read'+field.type.Struct, ['ar'], [`self.${field.name}`]), + ], + ] + ]), showlog && [StrStmt(`log.Println("skip", cc4)`)]), + CallCheckAssign('ReadDummy', ['ar', 'int(ar.N)'], ['_']), + ]) + ]; + + var ReadCommnType = (name, type) => { + if (type.struct) + return CallCheckAssign( + 'Read'+type.Struct, ['r'], [name]); + return [ + //DebugStmt(type), + CallCheckAssign( + 'Read'+type.fn, ['r', type.len].nonull(), [name]), ] }; - stmts = stmts.concat(opts.fields.map(field => { - if (field.sizelen) { - var arr = 'self.'+field.name; - return [ - DeclVar('count', 'int'), - CallCheckAssign('ReadInt', ['r', field.sizelen], ['count']), - For(RangeN('i', 'count'), readElemStmts(field)), - ]; - } else if (field.elem) { - var cond = field.count ? RangeN('i', field.count) : StrStmt('r.N > 0'); - return For(cond, readElemStmts(field)); - } else if (!field.hide) { - return CallCheckAssign('Read'+field.fn, ['r', field.len].nonull(), ['self.'+field.name]); - } - }).nonull()); + var ReadField = (name, type) => { + if (name == '_') + return CallCheckAssign('ReadDummy', ['r', type.len], ['_']); + if (type.arr && type.fn != 'Bytes') + return ReadArr('self.'+name, type); + return ReadCommnType('self.'+name, type); + }; - if (opts.resIsPtr) - stmts = stmts.concat([StrStmt(`res = self`)]); + var ReadFields = () => opts.fields.map(field => { + var name = field.name; + var type = field.type; + return ReadField(name, type); + }).nonull(); + + var ptr = opts.cc4; return Func( - 'Read'+opts.fnName, + 'Read'+opts.type, [['r', '*io.LimitedReader']], - [[opts.resIsPtr?'res':'self', (opts.resIsPtr?'*':'')+opts.atomType], ['err', 'error']], - stmts + [[ptr?'res':'self', (ptr?'*':'')+opts.type], ['err', 'error']], + [ + ptr && StrStmt(`self := &${opts.type}{}`), + !opts.atoms ? ReadFields() : ReadAtoms(), + ptr && StrStmt(`res = self`), + ] ); }; @@ -237,16 +312,21 @@ var D = (cls, ...fields) => { }; D('Func', 'name', 'args', 'rets', 'body'); -D('CallCheckAssign', 'fn', 'args', 'rets'); +D('CallCheckAssign', 'fn', 'args', 'rets', 'action'); D('DeclVar', 'name', 'type'); D('For', 'cond', 'body'); D('RangeN', 'i', 'n'); D('DeclStruct', 'name', 'body'); D('StrStmt', 'content'); +D('Switch', 'cond', 'cases', 'default'); + +var showlog = false; +var S = s => s && s || ''; var dumpFn = f => { var dumpArgs = x => x.map(x => x.join(' ')).join(','); return `func ${f.name}(${dumpArgs(f.args)}) (${dumpArgs(f.rets)}) { + ${S(showlog && 'log.Println("'+f.name+'")')} ${dumpStmts(f.body)} return }`; @@ -258,7 +338,7 @@ var dumpStmts = stmts => { return dumpStmts(stmt); } if (stmt.cls == 'CallCheckAssign') { return `if ${stmt.rets.concat(['err']).join(',')} = ${stmt.fn}(${stmt.args.join(',')}); err != nil { - return + ${stmt.action ? stmt.action : 'return'} }`; } else if (stmt.cls == 'DeclVar') { return `var ${stmt.name} ${stmt.type}`; @@ -276,68 +356,103 @@ var dumpStmts = stmts => { return dumpFn(stmt); } else if (stmt.cls == 'StrStmt') { return stmt.content; + } else if (stmt.cls == 'Switch') { + var dumpCase = c => `case ${c[0]}: { ${dumpStmts(c[1])} }`; + var dumpDefault = c => `default: { ${dumpStmts(c)} }`; + return `switch ${stmt.cond} { + ${stmt.cases.map(dumpCase).join('\n')} + ${stmt.default && dumpDefault(stmt.default) || ''} + }`; } }; - return stmts.map(dumpStmt).join('\n') + return stmts.nonull().map(dumpStmt).join('\n') }; -(() => { - var len = 3; - var f = Func('Readxx', [['f', '*io.LimitedReader']], [['res', '*xx'], ['err', 'error']], [ - CallCheckAssign('ReadInt', ['f', len], ['self.xx']), - CallCheckAssign('WriteInt', ['f', len], ['self.xx']), - DeclVar('n', 'int'), - For(RangeN('i', 'n'), [ - CallCheckAssign('WriteInt', ['f', len], ['self.xx']), - DeclStruct('hi', [['a', 'b'], ['c', 'd'], ['e', 'f']]), - ]), - ]); - console.log(dumpFn(f)); -}); +var parseType = s => { + var r = {}; + var bracket = /^\[(.*)\]/; + if (s.match(bracket)) { + var count = s.match(bracket)[1]; + if (count.substr(0,3) == 'int') { + r.varcount = +count.substr(3)/8; + } else { + r.count = +count; + } + r.arr = true; + s = s.replace(bracket, ''); + } + if (s.substr(0,1) == '*') { + r.ptr = true; + s = s.slice(1); + } + var types = /^(int|TimeStamp|byte|cc)/; + if (s.match(types)) { + r.type = s.match(types)[0]; + r.fn = uc(r.type); + s = s.replace(types, ''); + } + if (r.type == 'byte' && r.arr) { + r.len = r.count; + r.fn = 'Bytes'; + } + var lenDiv = 8; + if (r.type == 'cc') { + r.fn = 'String'; + r.type = 'string'; + lenDiv = 1; + } + var number = /[0-9]+/; + if (s.match(number)) { + r.len = +s.match(number)[0]/lenDiv; + s = s.replace(number, ''); + } + if (s != '') { + r.struct = s; + r.Struct = uc(s); + } + return r; +}; + +var typeStr = (t) => { + var s = ''; + if (t.arr) + s += '['+(t.count||'')+']'; + if (t.ptr) + s += '*'; + if (t.struct) + s += t.Struct; + if (t.type) + s += t.type; + return s; +}; + +var nameShouldHide = (name) => name == '_' var allStmts = () => { var stmts = []; - var convStructFields = fields => { - var typeStr = field => ( - field.cls == 'AtomPtr' || field.cls == 'StructPtr') ? '*'+field.type : field.type; - - return fields.filter(field => !field.hide) - .map(field => { - if (field.cls == 'Arr' || field.cls == 'LenArr') - return [field.name, '[]'+typeStr(field.elem)]; - return [field.name, typeStr(field)]; - }); - }; - for (var k in atoms) { - var list = atoms[k]; - var name = uc(k)+'Atom'; - var cc4 = list[0]; - var fields = list.slice(1); + var atom = atoms[k]; - stmts = stmts.concat([ - DeclStruct(name, convStructFields(fields)), - genReadStmts({ - cc4: cc4, - fields: fields, - fnName: name, - atomType: name, - resIsPtr: true, - }), - ]); - } - - for (var k in structs) { - var fields = structs[k]; var name = uc(k); + var fields = (atom.fields || atom.atoms).map(field => { + return { + name: uc(field[0]), + type: parseType(field[1]), + }; + }); stmts = stmts.concat([ - DeclStruct(name, convStructFields(fields)), - genReadStmts({ + DeclStruct(name, fields.map(field => !nameShouldHide(field.name) && [ + uc(field.name), + typeStr(field.type), + ]).nonull()), + + DeclReadFunc({ + type: name, fields: fields, - fnName: name, - atomType: name, + cc4: atom.cc4, + atoms: atom.atoms != null, }), ]); } @@ -345,10 +460,12 @@ var allStmts = () => { return stmts; }; -console.log(`// THIS FILE IS AUTO GENERATED +console.log(` +// THIS FILE IS AUTO GENERATED package atom import ( "io" + ${showlog && '"log"' || ''} ) `, dumpStmts(allStmts())); diff --git a/atom/reader.go b/atom/reader.go index 4af4a3a..ec6edcf 100644 --- a/atom/reader.go +++ b/atom/reader.go @@ -4,6 +4,7 @@ package atom import ( "io" "io/ioutil" + "log" ) type Fixed32 uint32 @@ -17,10 +18,6 @@ func ReadBytes(r io.Reader, n int) (res []byte, err error) { return } -func ReadBytesLeft(r *io.LimitedReader) (res []byte, err error) { - return ReadBytes(r, int(r.N)) -} - func ReadUInt(r io.Reader, n int) (res uint, err error) { var b []byte if b, err = ReadBytes(r, n); err != nil { @@ -74,42 +71,38 @@ func ReadDummy(r io.Reader, n int) (res int, err error) { return } -/* -func (self Reader) ReadAtom(atom Atom) (res Atom, err error) { +func ReadAtomHeader(r io.Reader, targetCC4 string) (res *io.LimitedReader, cc4 string, err error) { for { var size int - if size, err = self.ReadInt(4); err != nil { + if size, err = ReadInt(r, 4); err != nil { return } if size == 0 { continue } - var cc4 string - if cc4, err = self.ReadString(4); err != nil { + if cc4, err = ReadString(r, 4); err != nil { return } - if atom.CC4() != cc4 { - if err = self.Skip(size); err != nil { + size = size - 8 + + if false { + log.Println(cc4, targetCC4, size, cc4 == targetCC4) + } + + if targetCC4 != "" && cc4 != targetCC4 { + log.Println("ReadAtomHeader skip:", cc4) + if _, err = ReadDummy(r, size); err != nil { return } continue } - reader := &io.LimitedReader{ - R: self.Reader, - N: int64(size - 8), + res = &io.LimitedReader{ + R: r, + N: int64(size), } - if err = atom.Read(Reader{reader}); err != nil { - return - } - if err = self.Skip(int(reader.N)); err != nil { - return - } - - res = atom return } } -*/ diff --git a/atom/struct.go b/atom/struct.go index f0a240f..07b80a5 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -5,50 +5,67 @@ import ( "io" ) -type FileTypeAtom struct { - Movie *MovieAtom +type FileType struct { } -func ReadFileTypeAtom(r *io.LimitedReader) (res *FileTypeAtom, err error) { - self := &FileTypeAtom{} - if self.Movie, err = ReadMovieAtom(r); err != nil { - return - } +func ReadFileType(r *io.LimitedReader) (res *FileType, err error) { + + self := &FileType{} + res = self return } -type MovieAtom struct { - Header *MovieHeaderAtom - Tracks []*TrackAtom +type Movie struct { + Header *MovieHeader + Tracks []*Track } -func ReadMovieAtom(r *io.LimitedReader) (res *MovieAtom, err error) { - self := &MovieAtom{} - if self.Header, err = ReadMovieHeaderAtom(r); err != nil { - return - } +func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { + + self := &Movie{} + // ReadAtoms for r.N > 0 { - var item *TrackAtom - if item, err = ReadTrackAtom(r); err != nil { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "mvhd": + { + if self.Header, err = ReadMovieHeader(ar); err != nil { + return + } + } + case "trak": + { + var item *Track + if item, err = ReadTrack(ar); err != nil { + return + } + self.Tracks = append(self.Tracks, item) + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { return } - self.Tracks = append(self.Tracks, item) } res = self return } -type MovieHeaderAtom struct { +type MovieHeader struct { Version int Flags int CTime TimeStamp MTime TimeStamp - TimeScale int - Duration int + TimeScale TimeStamp + Duration TimeStamp PreferredRate int PreferredVolume int - Matrix []byte + Matrix [9]int PreviewTime TimeStamp PreviewDuration TimeStamp PosterTime TimeStamp @@ -58,8 +75,9 @@ type MovieHeaderAtom struct { NextTrackId int } -func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) { - self := &MovieHeaderAtom{} +func ReadMovieHeader(r *io.LimitedReader) (res *MovieHeader, err error) { + + self := &MovieHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -72,10 +90,10 @@ func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) if self.MTime, err = ReadTimeStamp(r, 4); err != nil { return } - if self.TimeScale, err = ReadInt(r, 4); err != nil { + if self.TimeScale, err = ReadTimeStamp(r, 4); err != nil { return } - if self.Duration, err = ReadInt(r, 4); err != nil { + if self.Duration, err = ReadTimeStamp(r, 4); err != nil { return } if self.PreferredRate, err = ReadInt(r, 4); err != nil { @@ -84,9 +102,14 @@ func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) if self.PreferredVolume, err = ReadInt(r, 2); err != nil { return } - if self.Matrix, err = ReadBytes(r, 36); err != nil { + if _, err = ReadDummy(r, 10); err != nil { return } + for i := 0; i < 9; i++ { + if self.Matrix[i], err = ReadInt(r, 4); err != nil { + return + } + } if self.PreviewTime, err = ReadTimeStamp(r, 4); err != nil { return } @@ -112,40 +135,62 @@ func ReadMovieHeaderAtom(r *io.LimitedReader) (res *MovieHeaderAtom, err error) return } -type TrackAtom struct { - Header *TrackHeaderAtom - Media *MediaAtom +type Track struct { + Header *TrackHeader + Media *Media } -func ReadTrackAtom(r *io.LimitedReader) (res *TrackAtom, err error) { - self := &TrackAtom{} - if self.Header, err = ReadTrackHeaderAtom(r); err != nil { - return - } - if self.Media, err = ReadMediaAtom(r); err != nil { - return +func ReadTrack(r *io.LimitedReader) (res *Track, err error) { + + self := &Track{} + // ReadAtoms + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "tkhd": + { + if self.Header, err = ReadTrackHeader(ar); err != nil { + return + } + } + case "mdia": + { + if self.Media, err = ReadMedia(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } } res = self return } -type TrackHeaderAtom struct { +type TrackHeader struct { Version int Flags int CTime TimeStamp MTime TimeStamp - TrackId int - Duration int + TrackId TimeStamp + Duration TimeStamp Layer int AlternateGroup int Volume int - Matrix []byte - TrackWidth Fixed32 - TrackHeight Fixed32 + Matrix [9]int + TrackWidth int + TrackHeader int } -func ReadTrackHeaderAtom(r *io.LimitedReader) (res *TrackHeaderAtom, err error) { - self := &TrackHeaderAtom{} +func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { + + self := &TrackHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -158,10 +203,16 @@ func ReadTrackHeaderAtom(r *io.LimitedReader) (res *TrackHeaderAtom, err error) if self.MTime, err = ReadTimeStamp(r, 4); err != nil { return } - if self.TrackId, err = ReadInt(r, 4); err != nil { + if self.TrackId, err = ReadTimeStamp(r, 4); err != nil { return } - if self.Duration, err = ReadInt(r, 4); err != nil { + if _, err = ReadDummy(r, 4); err != nil { + return + } + if self.Duration, err = ReadTimeStamp(r, 4); err != nil { + return + } + if _, err = ReadDummy(r, 8); err != nil { return } if self.Layer, err = ReadInt(r, 2); err != nil { @@ -173,59 +224,86 @@ func ReadTrackHeaderAtom(r *io.LimitedReader) (res *TrackHeaderAtom, err error) if self.Volume, err = ReadInt(r, 2); err != nil { return } - if self.Matrix, err = ReadBytes(r, 36); err != nil { + if _, err = ReadDummy(r, 2); err != nil { return } - if self.TrackWidth, err = ReadFixed32(r, 4); err != nil { + for i := 0; i < 9; i++ { + if self.Matrix[i], err = ReadInt(r, 4); err != nil { + return + } + } + if self.TrackWidth, err = ReadInt(r, 4); err != nil { return } - if self.TrackHeight, err = ReadFixed32(r, 4); err != nil { + if self.TrackHeader, err = ReadInt(r, 4); err != nil { return } res = self return } -type MediaAtom struct { - Header *MediaHeaderAtom - Info *MediaInfoAtom +type Media struct { + Header *MediaHeader + Info *MediaInfo } -func ReadMediaAtom(r *io.LimitedReader) (res *MediaAtom, err error) { - self := &MediaAtom{} - if self.Header, err = ReadMediaHeaderAtom(r); err != nil { - return - } - if self.Info, err = ReadMediaInfoAtom(r); err != nil { - return +func ReadMedia(r *io.LimitedReader) (res *Media, err error) { + + self := &Media{} + // ReadAtoms + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "mdhd": + { + if self.Header, err = ReadMediaHeader(ar); err != nil { + return + } + } + case "minf": + { + if self.Info, err = ReadMediaInfo(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } } res = self return } -type MediaHeaderAtom struct { +type MediaHeader struct { Version int Flags int - CTime TimeStamp - MTime TimeStamp + CTime int + MTime int TimeScale int Duration int Language int Quality int } -func ReadMediaHeaderAtom(r *io.LimitedReader) (res *MediaHeaderAtom, err error) { - self := &MediaHeaderAtom{} +func ReadMediaHeader(r *io.LimitedReader) (res *MediaHeader, err error) { + + self := &MediaHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return } if self.Flags, err = ReadInt(r, 3); err != nil { return } - if self.CTime, err = ReadTimeStamp(r, 4); err != nil { + if self.CTime, err = ReadInt(r, 4); err != nil { return } - if self.MTime, err = ReadTimeStamp(r, 4); err != nil { + if self.MTime, err = ReadInt(r, 4); err != nil { return } if self.TimeScale, err = ReadInt(r, 4); err != nil { @@ -244,32 +322,86 @@ func ReadMediaHeaderAtom(r *io.LimitedReader) (res *MediaHeaderAtom, err error) return } -type MediaInfoAtom struct { - Video *VideoMediaInfoAtom - Sample *SampleTableAtom +type MediaInfo struct { + Sound *SoundMediaInfo + Video *VideoMediaInfo + Sample *SampleTable } -func ReadMediaInfoAtom(r *io.LimitedReader) (res *MediaInfoAtom, err error) { - self := &MediaInfoAtom{} - if self.Video, err = ReadVideoMediaInfoAtom(r); err != nil { +func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { + + self := &MediaInfo{} + // ReadAtoms + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "smhd": + { + if self.Sound, err = ReadSoundMediaInfo(ar); err != nil { + return + } + } + case "vmhd": + { + if self.Video, err = ReadVideoMediaInfo(ar); err != nil { + return + } + } + case "stbl": + { + if self.Sample, err = ReadSampleTable(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + res = self + return +} + +type SoundMediaInfo struct { + Version int + Flags int + Balance int +} + +func ReadSoundMediaInfo(r *io.LimitedReader) (res *SoundMediaInfo, err error) { + + self := &SoundMediaInfo{} + if self.Version, err = ReadInt(r, 1); err != nil { return } - if self.Sample, err = ReadSampleTableAtom(r); err != nil { + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.Balance, err = ReadInt(r, 2); err != nil { + return + } + if _, err = ReadDummy(r, 2); err != nil { return } res = self return } -type VideoMediaInfoAtom struct { +type VideoMediaInfo struct { Version int Flags int GraphicsMode int - Opcolor []int + Opcolor [3]int } -func ReadVideoMediaInfoAtom(r *io.LimitedReader) (res *VideoMediaInfoAtom, err error) { - self := &VideoMediaInfoAtom{} +func ReadVideoMediaInfo(r *io.LimitedReader) (res *VideoMediaInfo, err error) { + + self := &VideoMediaInfo{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -280,57 +412,96 @@ func ReadVideoMediaInfoAtom(r *io.LimitedReader) (res *VideoMediaInfoAtom, err e return } for i := 0; i < 3; i++ { - var item int - if item, err = ReadInt(r, 2); err != nil { + if self.Opcolor[i], err = ReadInt(r, 2); err != nil { return } - self.Opcolor = append(self.Opcolor, item) } res = self return } -type SampleTableAtom struct { - SampleDesc *SampleDescAtom - TimeToSample *TimeToSampleAtom - CompositionOffset *CompositionOffsetAtom - SyncSample *SyncSampleAtom - SampleSize *SampleSizeAtom - ChunkOffset *ChunkOffsetAtom +type SampleTable struct { + SampleDesc *SampleDesc + TimeToSample *TimeToSample + CompositionOffset *CompositionOffset + SampleToChunk *SampleToChunk + SyncSample *SyncSample + ChunkOffset *ChunkOffset + SampleSize *SampleSize } -func ReadSampleTableAtom(r *io.LimitedReader) (res *SampleTableAtom, err error) { - self := &SampleTableAtom{} - if self.SampleDesc, err = ReadSampleDescAtom(r); err != nil { - return - } - if self.TimeToSample, err = ReadTimeToSampleAtom(r); err != nil { - return - } - if self.CompositionOffset, err = ReadCompositionOffsetAtom(r); err != nil { - return - } - if self.SyncSample, err = ReadSyncSampleAtom(r); err != nil { - return - } - if self.SampleSize, err = ReadSampleSizeAtom(r); err != nil { - return - } - if self.ChunkOffset, err = ReadChunkOffsetAtom(r); err != nil { - return +func ReadSampleTable(r *io.LimitedReader) (res *SampleTable, err error) { + + self := &SampleTable{} + // ReadAtoms + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "stsd": + { + if self.SampleDesc, err = ReadSampleDesc(ar); err != nil { + return + } + } + case "stts": + { + if self.TimeToSample, err = ReadTimeToSample(ar); err != nil { + return + } + } + case "ctts": + { + if self.CompositionOffset, err = ReadCompositionOffset(ar); err != nil { + return + } + } + case "stsc": + { + if self.SampleToChunk, err = ReadSampleToChunk(ar); err != nil { + return + } + } + case "stss": + { + if self.SyncSample, err = ReadSyncSample(ar); err != nil { + return + } + } + case "stco": + { + if self.ChunkOffset, err = ReadChunkOffset(ar); err != nil { + return + } + } + case "stsz": + { + if self.SampleSize, err = ReadSampleSize(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } } res = self return } -type SampleDescAtom struct { +type SampleDesc struct { Version int Flags int - Entries []SampleDescEntry + Entries []*SampleDescEntry } -func ReadSampleDescAtom(r *io.LimitedReader) (res *SampleDescAtom, err error) { - self := &SampleDescAtom{} +func ReadSampleDesc(r *io.LimitedReader) (res *SampleDesc, err error) { + + self := &SampleDesc{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -341,25 +512,25 @@ func ReadSampleDescAtom(r *io.LimitedReader) (res *SampleDescAtom, err error) { if count, err = ReadInt(r, 4); err != nil { return } + self.Entries = make([]*SampleDescEntry, count) for i := 0; i < count; i++ { - var item SampleDescEntry - if item, err = ReadSampleDescEntry(r); err != nil { + if self.Entries[i], err = ReadSampleDescEntry(r); err != nil { return } - self.Entries = append(self.Entries, item) } res = self return } -type TimeToSampleAtom struct { +type TimeToSample struct { Version int Flags int Entries []TimeToSampleEntry } -func ReadTimeToSampleAtom(r *io.LimitedReader) (res *TimeToSampleAtom, err error) { - self := &TimeToSampleAtom{} +func ReadTimeToSample(r *io.LimitedReader) (res *TimeToSample, err error) { + + self := &TimeToSample{} if self.Version, err = ReadInt(r, 1); err != nil { return } @@ -370,158 +541,23 @@ func ReadTimeToSampleAtom(r *io.LimitedReader) (res *TimeToSampleAtom, err error if count, err = ReadInt(r, 4); err != nil { return } + self.Entries = make([]TimeToSampleEntry, count) for i := 0; i < count; i++ { - var item TimeToSampleEntry - if item, err = ReadTimeToSampleEntry(r); err != nil { + if self.Entries[i], err = ReadTimeToSampleEntry(r); err != nil { return } - self.Entries = append(self.Entries, item) } res = self return } -type CompositionOffsetAtom struct { - Version int - Flags int - Entries []CompositionOffsetEntry -} - -func ReadCompositionOffsetAtom(r *io.LimitedReader) (res *CompositionOffsetAtom, err error) { - self := &CompositionOffsetAtom{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - var count int - if count, err = ReadInt(r, 4); err != nil { - return - } - for i := 0; i < count; i++ { - var item CompositionOffsetEntry - if item, err = ReadCompositionOffsetEntry(r); err != nil { - return - } - self.Entries = append(self.Entries, item) - } - res = self - return -} - -type SyncSampleAtom struct { - Version int - Flags int - Entries []int -} - -func ReadSyncSampleAtom(r *io.LimitedReader) (res *SyncSampleAtom, err error) { - self := &SyncSampleAtom{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - var count int - if count, err = ReadInt(r, 4); err != nil { - return - } - for i := 0; i < count; i++ { - var item int - if item, err = ReadInt(r, 4); err != nil { - return - } - self.Entries = append(self.Entries, item) - } - res = self - return -} - -type SampleSizeAtom struct { - Version int - Flags int - Entries []int -} - -func ReadSampleSizeAtom(r *io.LimitedReader) (res *SampleSizeAtom, err error) { - self := &SampleSizeAtom{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - var count int - if count, err = ReadInt(r, 4); err != nil { - return - } - for i := 0; i < count; i++ { - var item int - if item, err = ReadInt(r, 4); err != nil { - return - } - self.Entries = append(self.Entries, item) - } - res = self - return -} - -type ChunkOffsetAtom struct { - Version int - Flags int - Entries []int -} - -func ReadChunkOffsetAtom(r *io.LimitedReader) (res *ChunkOffsetAtom, err error) { - self := &ChunkOffsetAtom{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - var count int - if count, err = ReadInt(r, 4); err != nil { - return - } - for i := 0; i < count; i++ { - var item int - if item, err = ReadInt(r, 4); err != nil { - return - } - self.Entries = append(self.Entries, item) - } - res = self - return -} - -type SampleDescEntry struct { - Format string - DataRefIdx int - Data []byte -} - -func ReadSampleDescEntry(r *io.LimitedReader) (self SampleDescEntry, err error) { - if self.Format, err = ReadString(r, 4); err != nil { - return - } - if self.DataRefIdx, err = ReadInt(r, 2); err != nil { - return - } - if self.Data, err = ReadBytesLeft(r); err != nil { - return - } - return -} - type TimeToSampleEntry struct { Count int Duration int } func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err error) { + if self.Count, err = ReadInt(r, 4); err != nil { return } @@ -531,12 +567,91 @@ func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err err return } +type SampleToChunk struct { + Version int + Flags int + Entries []SampleToChunkEntry +} + +func ReadSampleToChunk(r *io.LimitedReader) (res *SampleToChunk, err error) { + + self := &SampleToChunk{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]SampleToChunkEntry, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadSampleToChunkEntry(r); err != nil { + return + } + } + res = self + return +} + +type SampleToChunkEntry struct { + FirstChunk int + SamplesPerChunk int + SampleDescId int +} + +func ReadSampleToChunkEntry(r *io.LimitedReader) (self SampleToChunkEntry, err error) { + + if self.FirstChunk, err = ReadInt(r, 4); err != nil { + return + } + if self.SamplesPerChunk, err = ReadInt(r, 4); err != nil { + return + } + if self.SampleDescId, err = ReadInt(r, 4); err != nil { + return + } + return +} + +type CompositionOffset struct { + Version int + Flags int + Entries []int +} + +func ReadCompositionOffset(r *io.LimitedReader) (res *CompositionOffset, err error) { + + self := &CompositionOffset{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]int, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadInt(r, 4); err != nil { + return + } + } + res = self + return +} + type CompositionOffsetEntry struct { Count int Offset int } func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntry, err error) { + if self.Count, err = ReadInt(r, 4); err != nil { return } @@ -545,3 +660,90 @@ func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntr } return } + +type SyncSample struct { + Version int + Flags int + Entries []int +} + +func ReadSyncSample(r *io.LimitedReader) (res *SyncSample, err error) { + + self := &SyncSample{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]int, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadInt(r, 4); err != nil { + return + } + } + res = self + return +} + +type SampleSize struct { + Version int + Flags int + Entries []int +} + +func ReadSampleSize(r *io.LimitedReader) (res *SampleSize, err error) { + + self := &SampleSize{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]int, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadInt(r, 4); err != nil { + return + } + } + res = self + return +} + +type ChunkOffset struct { + Version int + Flags int + Entries []int +} + +func ReadChunkOffset(r *io.LimitedReader) (res *ChunkOffset, err error) { + + self := &ChunkOffset{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + var count int + if count, err = ReadInt(r, 4); err != nil { + return + } + self.Entries = make([]int, count) + for i := 0; i < count; i++ { + if self.Entries[i], err = ReadInt(r, 4); err != nil { + return + } + } + res = self + return +} diff --git a/example/read.go b/example/read.go new file mode 100644 index 0000000..56d07c1 --- /dev/null +++ b/example/read.go @@ -0,0 +1,15 @@ + +package main + +import ( + mp4 "./.." + "log" +) + +func main() { + if _, err := mp4.Open("tiny2-avconv.mp4"); err != nil { + log.Println(err) + return + } +} + diff --git a/mp4.go b/mp4.go index 347c4ca..73bfb3d 100644 --- a/mp4.go +++ b/mp4.go @@ -4,11 +4,11 @@ package mp4 import ( "./atom" "os" + "io" + "log" ) type File struct { - moov *atom.Moov - ftyp *atom.Ftyp } func (self *File) AddAvcc(avcc *Avcc) { @@ -37,19 +37,34 @@ func Open(filename string) (file *File, err error) { return } - var entry atom.Atom - file = &File{} - r := atom.Reader{osfile} - - if entry, err = r.ReadAtom(&atom.Ftyp{}); err != nil { + var finfo os.FileInfo + if finfo, err = osfile.Stat(); err != nil { return } - file.ftyp = entry.(*atom.Ftyp) + log.Println("filesize", finfo.Size()) - if entry, err = r.ReadAtom(&atom.Moov{}); err != nil { + lr := &io.LimitedReader{R: osfile, N: finfo.Size()} + + for lr.N > 0 { + var r *io.LimitedReader + var cc4 string + if r, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { + return + } + if cc4 == "moov" { + var moov *atom.Movie + if moov, err = atom.ReadMovie(r); err != nil { + return + } + log.Println("tracks nr", len(moov.Tracks)) + } + log.Println("atom", cc4, "left", lr.N) + atom.ReadDummy(r, int(r.N)) + } + + if _, err = os.Create(filename+".out.mp4"); err != nil { return } - file.moov = entry.(*atom.Moov) return } From 637f47b25024a439c195a602508041b561d52283 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 21 Nov 2015 19:49:03 +0800 Subject: [PATCH 04/93] add hdlr and fix bugs make vlc can play --- atom/genStruct.js | 105 ++++++- atom/otherStruct.go | 114 ++++++++ atom/struct.go | 684 +++++++++++++++++++++++++++++++++++++++++--- atom/writer.go | 80 ++++++ mp4.go | 55 ++-- 5 files changed, 972 insertions(+), 66 deletions(-) create mode 100644 atom/otherStruct.go create mode 100644 atom/writer.go diff --git a/atom/genStruct.js b/atom/genStruct.js index 6c68689..61630c0 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -71,11 +71,16 @@ var atoms = { ], }, + handlerRefer: { + cc4: 'hdlr', + }, + media: { cc4: 'mdia', atoms: [ ['header', '*mediaHeader'], ['info', '*mediaInfo'], + ['hdlr', '*handlerRefer'], ], }, @@ -182,7 +187,7 @@ var atoms = { fields: [ ['version', 'int8'], ['flags', 'int24'], - ['entries', '[int32]int32'], + ['entries', '[int32]compositionOffsetEntry'], ], }, @@ -207,6 +212,7 @@ var atoms = { fields: [ ['version', 'int8'], ['flags', 'int24'], + ['sampleSize', 'int32'], ['entries', '[int32]int32'], ], }, @@ -225,7 +231,7 @@ var atoms = { var DeclReadFunc = (opts) => { var stmts = []; - var DebugStmt = type => StrStmt(`// ${JSON.stringify(type)}`); + var DebugStmt = type => `// ${JSON.stringify(type)}`; var ReadArr = (name, type) => { return [ @@ -234,7 +240,7 @@ var DeclReadFunc = (opts) => { type.varcount && [ DeclVar('count', 'int'), CallCheckAssign('ReadInt', ['r', type.varcount], ['count']), - StrStmt(`${name} = make(${typeStr(type)}, count)`), + `${name} = make(${typeStr(type)}, count)`, ], For(RangeN('i', type.varcount ? 'count' : type.count), [ ReadCommnType(name+'[i]', type), @@ -244,8 +250,7 @@ var DeclReadFunc = (opts) => { var elemTypeStr = type => typeStr(Object.assign({}, type, {arr: false})); var ReadAtoms = () => [ - StrStmt(`// ReadAtoms`), - For(StrStmt(`r.N > 0`), [ + For(`r.N > 0`, [ DeclVar('cc4', 'string'), DeclVar('ar', '*io.LimitedReader'), CallCheckAssign('ReadAtomHeader', ['r', '""'], ['ar', 'cc4']), @@ -254,12 +259,12 @@ var DeclReadFunc = (opts) => { field.type.arr ? [ DeclVar('item', elemTypeStr(field.type)), CallCheckAssign('Read'+field.type.Struct, ['ar'], ['item']), - StrStmt(`self.${field.name} = append(self.${field.name}, item)`), + `self.${field.name} = append(self.${field.name}, item)`, ] : [ CallCheckAssign('Read'+field.type.Struct, ['ar'], [`self.${field.name}`]), ], ] - ]), showlog && [StrStmt(`log.Println("skip", cc4)`)]), + ]), showlog && [`log.Println("skip", cc4)`]), CallCheckAssign('ReadDummy', ['ar', 'int(ar.N)'], ['_']), ]) ]; @@ -296,9 +301,70 @@ var DeclReadFunc = (opts) => { [['r', '*io.LimitedReader']], [[ptr?'res':'self', (ptr?'*':'')+opts.type], ['err', 'error']], [ - ptr && StrStmt(`self := &${opts.type}{}`), + ptr && `self := &${opts.type}{}`, !opts.atoms ? ReadFields() : ReadAtoms(), - ptr && StrStmt(`res = self`), + ptr && `res = self`, + ] + ); +}; + +var DeclWriteFunc = (opts) => { + var SavePos = [ + DeclVar('aw', '*Writer'), + CallCheckAssign('WriteAtomHeader', ['w', `"${opts.cc4}"`], ['aw']), + `w = aw`, + ]; + + var RestorePosSetSize = [ + CallCheckAssign('aw.Close', [], []), + ]; + + var WriteAtoms = () => opts.fields.map(field => { + var name = 'self.'+field.name; + return [ + `if ${name} != nil {`, + field.type.arr ? WriteArr(name, field.type) : WriteCommnType(name, field.type), + `}`, + ]; + }); + + var WriteArr = (name, type) => { + return [ + type.varcount && CallCheckAssign('WriteInt', ['w', `len(${name})`, type.varcount], []), + For(`_, elem := range ${name}`, [ + WriteCommnType('elem', type), + ]), + ]; + }; + + var WriteCommnType = (name, type) => { + if (type.struct) + return CallCheckAssign( + 'Write'+type.Struct, ['w', name], []); + return [ + CallCheckAssign( + 'Write'+type.fn, ['w', name, type.len].nonull(), []), + ] + }; + + var WriteField = (name, type) => { + if (name == '_') + return CallCheckAssign('WriteDummy', ['w', type.len], []); + if (type.arr && type.fn != 'Bytes') + return WriteArr('self.'+name, type); + return WriteCommnType('self.'+name, type); + }; + + var WriteFields = () => opts.fields.map(field => WriteField(field.name, field.type)); + + return Func( + 'Write'+opts.type, + [['w', 'io.WriteSeeker'], ['self', (opts.cc4?'*':'')+opts.type]], + [['err', 'error']], + [ + opts.cc4 && SavePos, + opts.atoms ? WriteAtoms() : WriteFields(), + opts.cc4 && RestorePosSetSize, ] ); }; @@ -320,7 +386,7 @@ D('DeclStruct', 'name', 'body'); D('StrStmt', 'content'); D('Switch', 'cond', 'cases', 'default'); -var showlog = false; +var showlog = true; var S = s => s && s || ''; var dumpFn = f => { @@ -334,7 +400,9 @@ var dumpFn = f => { var dumpStmts = stmts => { var dumpStmt = stmt => { - if (stmt instanceof Array) { + if (typeof(stmt) == 'string') { + return stmt; + } else if (stmt instanceof Array) { return dumpStmts(stmt); } if (stmt.cls == 'CallCheckAssign') { return `if ${stmt.rets.concat(['err']).join(',')} = ${stmt.fn}(${stmt.args.join(',')}); err != nil { @@ -433,9 +501,13 @@ var allStmts = () => { for (var k in atoms) { var atom = atoms[k]; - var name = uc(k); - var fields = (atom.fields || atom.atoms).map(field => { + + var fields = (atom.fields || atom.atoms); + if (fields == null) + continue; + + fields = fields.map(field => { return { name: uc(field[0]), type: parseType(field[1]), @@ -454,6 +526,13 @@ var allStmts = () => { cc4: atom.cc4, atoms: atom.atoms != null, }), + + DeclWriteFunc({ + type: name, + fields: fields, + cc4: atom.cc4, + atoms: atom.atoms != null, + }), ]); } diff --git a/atom/otherStruct.go b/atom/otherStruct.go new file mode 100644 index 0000000..633fd28 --- /dev/null +++ b/atom/otherStruct.go @@ -0,0 +1,114 @@ + +package atom + +import ( + "io" +) + +type SampleDescEntry struct { + Format string + DataRefIndex int + Data []byte +} + +func ReadSampleDescEntry(r *io.LimitedReader) (res *SampleDescEntry, err error) { + self := &SampleDescEntry{} + if r, self.Format, err = ReadAtomHeader(r, ""); err != nil { + return + } + if _, err = ReadDummy(r, 6); err != nil { + return + } + if self.DataRefIndex, err = ReadInt(r, 2); err != nil { + return + } + if self.Data, err = ReadBytes(r, int(r.N)); err != nil { + return + } + res = self + return +} + +func WriteSampleDescEntry(w io.WriteSeeker, self *SampleDescEntry) (err error) { + var aw *Writer + if aw, err = WriteAtomHeader(w, self.Format); err != nil { + return + } + w = aw + if err = WriteDummy(w, 6); err != nil { + return + } + if err = WriteInt(w, self.DataRefIndex, 2); err != nil { + return + } + if err = WriteBytes(w, self.Data); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} + +type HandlerRefer struct { + Version int + Flags int + Type string + SubType string + Name string +} + +func ReadHandlerRefer(r *io.LimitedReader) (res *HandlerRefer, err error) { + self := &HandlerRefer{} + if self.Version, err = ReadInt(r, 3); err != nil { + return + } + if self.Flags, err = ReadInt(r, 1); err != nil { + return + } + if self.Type, err = ReadString(r, 4); err != nil { + return + } + if self.SubType, err = ReadString(r, 4); err != nil { + return + } + if _, err = ReadDummy(r, 12); err != nil { + return + } + if self.Name, err = ReadString(r, int(r.N)); err != nil { + return + } + res = self + return +} + +func WriteHandlerRefer(w io.WriteSeeker, self *HandlerRefer) (err error) { + var aw *Writer + if aw, err = WriteAtomHeader(w, "hdlr"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 3); err != nil { + return + } + if err = WriteInt(w, self.Flags, 1); err != nil { + return + } + if err = WriteString(w, self.Type); err != nil { + return + } + if err = WriteString(w, self.SubType); err != nil { + return + } + if err = WriteDummy(w, 12); err != nil { + return + } + if err = WriteString(w, self.Name); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} + diff --git a/atom/struct.go b/atom/struct.go index 07b80a5..b167f7a 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -3,18 +3,32 @@ package atom import ( "io" + "log" ) type FileType struct { } func ReadFileType(r *io.LimitedReader) (res *FileType, err error) { - + log.Println("ReadFileType") self := &FileType{} res = self return } +func WriteFileType(w io.WriteSeeker, self *FileType) (err error) { + log.Println("WriteFileType") + var aw *Writer + if aw, err = WriteAtomHeader(w, "ftyp"); err != nil { + return + } + w = aw + + if err = aw.Close(); err != nil { + return + } + return +} type Movie struct { Header *MovieHeader @@ -22,9 +36,8 @@ type Movie struct { } func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { - + log.Println("ReadMovie") self := &Movie{} - // ReadAtoms for r.N > 0 { var cc4 string var ar *io.LimitedReader @@ -46,7 +59,10 @@ func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { } self.Tracks = append(self.Tracks, item) } - + default: + { + log.Println("skip", cc4) + } } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -55,6 +71,30 @@ func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { res = self return } +func WriteMovie(w io.WriteSeeker, self *Movie) (err error) { + log.Println("WriteMovie") + var aw *Writer + if aw, err = WriteAtomHeader(w, "moov"); err != nil { + return + } + w = aw + if self.Header != nil { + if err = WriteMovieHeader(w, self.Header); err != nil { + return + } + } + if self.Tracks != nil { + for _, elem := range self.Tracks { + if err = WriteTrack(w, elem); err != nil { + return + } + } + } + if err = aw.Close(); err != nil { + return + } + return +} type MovieHeader struct { Version int @@ -76,7 +116,7 @@ type MovieHeader struct { } func ReadMovieHeader(r *io.LimitedReader) (res *MovieHeader, err error) { - + log.Println("ReadMovieHeader") self := &MovieHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -134,6 +174,71 @@ func ReadMovieHeader(r *io.LimitedReader) (res *MovieHeader, err error) { res = self return } +func WriteMovieHeader(w io.WriteSeeker, self *MovieHeader) (err error) { + log.Println("WriteMovieHeader") + var aw *Writer + if aw, err = WriteAtomHeader(w, "mvhd"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteTimeStamp(w, self.CTime, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.MTime, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.TimeScale, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.Duration, 4); err != nil { + return + } + if err = WriteInt(w, self.PreferredRate, 4); err != nil { + return + } + if err = WriteInt(w, self.PreferredVolume, 2); err != nil { + return + } + if err = WriteDummy(w, 10); err != nil { + return + } + for _, elem := range self.Matrix { + if err = WriteInt(w, elem, 4); err != nil { + return + } + } + if err = WriteTimeStamp(w, self.PreviewTime, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.PreviewDuration, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.PosterTime, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.SelectionTime, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.SelectionDuration, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.CurrentTime, 4); err != nil { + return + } + if err = WriteInt(w, self.NextTrackId, 4); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} type Track struct { Header *TrackHeader @@ -141,9 +246,8 @@ type Track struct { } func ReadTrack(r *io.LimitedReader) (res *Track, err error) { - + log.Println("ReadTrack") self := &Track{} - // ReadAtoms for r.N > 0 { var cc4 string var ar *io.LimitedReader @@ -163,7 +267,10 @@ func ReadTrack(r *io.LimitedReader) (res *Track, err error) { return } } - + default: + { + log.Println("skip", cc4) + } } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -172,6 +279,28 @@ func ReadTrack(r *io.LimitedReader) (res *Track, err error) { res = self return } +func WriteTrack(w io.WriteSeeker, self *Track) (err error) { + log.Println("WriteTrack") + var aw *Writer + if aw, err = WriteAtomHeader(w, "trak"); err != nil { + return + } + w = aw + if self.Header != nil { + if err = WriteTrackHeader(w, self.Header); err != nil { + return + } + } + if self.Media != nil { + if err = WriteMedia(w, self.Media); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type TrackHeader struct { Version int @@ -189,7 +318,7 @@ type TrackHeader struct { } func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { - + log.Println("ReadTrackHeader") self := &TrackHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -241,16 +370,75 @@ func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { res = self return } +func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { + log.Println("WriteTrackHeader") + var aw *Writer + if aw, err = WriteAtomHeader(w, "tkhd"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteTimeStamp(w, self.CTime, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.MTime, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.TrackId, 4); err != nil { + return + } + if err = WriteDummy(w, 4); err != nil { + return + } + if err = WriteTimeStamp(w, self.Duration, 4); err != nil { + return + } + if err = WriteDummy(w, 8); err != nil { + return + } + if err = WriteInt(w, self.Layer, 2); err != nil { + return + } + if err = WriteInt(w, self.AlternateGroup, 2); err != nil { + return + } + if err = WriteInt(w, self.Volume, 2); err != nil { + return + } + if err = WriteDummy(w, 2); err != nil { + return + } + for _, elem := range self.Matrix { + if err = WriteInt(w, elem, 4); err != nil { + return + } + } + if err = WriteInt(w, self.TrackWidth, 4); err != nil { + return + } + if err = WriteInt(w, self.TrackHeader, 4); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} type Media struct { Header *MediaHeader Info *MediaInfo + Hdlr *HandlerRefer } func ReadMedia(r *io.LimitedReader) (res *Media, err error) { - + log.Println("ReadMedia") self := &Media{} - // ReadAtoms for r.N > 0 { var cc4 string var ar *io.LimitedReader @@ -270,7 +458,16 @@ func ReadMedia(r *io.LimitedReader) (res *Media, err error) { return } } - + case "hdlr": + { + if self.Hdlr, err = ReadHandlerRefer(ar); err != nil { + return + } + } + default: + { + log.Println("skip", cc4) + } } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -279,6 +476,33 @@ func ReadMedia(r *io.LimitedReader) (res *Media, err error) { res = self return } +func WriteMedia(w io.WriteSeeker, self *Media) (err error) { + log.Println("WriteMedia") + var aw *Writer + if aw, err = WriteAtomHeader(w, "mdia"); err != nil { + return + } + w = aw + if self.Header != nil { + if err = WriteMediaHeader(w, self.Header); err != nil { + return + } + } + if self.Info != nil { + if err = WriteMediaInfo(w, self.Info); err != nil { + return + } + } + if self.Hdlr != nil { + if err = WriteHandlerRefer(w, self.Hdlr); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type MediaHeader struct { Version int @@ -292,7 +516,7 @@ type MediaHeader struct { } func ReadMediaHeader(r *io.LimitedReader) (res *MediaHeader, err error) { - + log.Println("ReadMediaHeader") self := &MediaHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -321,6 +545,42 @@ func ReadMediaHeader(r *io.LimitedReader) (res *MediaHeader, err error) { res = self return } +func WriteMediaHeader(w io.WriteSeeker, self *MediaHeader) (err error) { + log.Println("WriteMediaHeader") + var aw *Writer + if aw, err = WriteAtomHeader(w, "mdhd"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, self.CTime, 4); err != nil { + return + } + if err = WriteInt(w, self.MTime, 4); err != nil { + return + } + if err = WriteInt(w, self.TimeScale, 4); err != nil { + return + } + if err = WriteInt(w, self.Duration, 4); err != nil { + return + } + if err = WriteInt(w, self.Language, 2); err != nil { + return + } + if err = WriteInt(w, self.Quality, 2); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} type MediaInfo struct { Sound *SoundMediaInfo @@ -329,9 +589,8 @@ type MediaInfo struct { } func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { - + log.Println("ReadMediaInfo") self := &MediaInfo{} - // ReadAtoms for r.N > 0 { var cc4 string var ar *io.LimitedReader @@ -357,7 +616,10 @@ func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { return } } - + default: + { + log.Println("skip", cc4) + } } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -366,6 +628,33 @@ func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { res = self return } +func WriteMediaInfo(w io.WriteSeeker, self *MediaInfo) (err error) { + log.Println("WriteMediaInfo") + var aw *Writer + if aw, err = WriteAtomHeader(w, "minf"); err != nil { + return + } + w = aw + if self.Sound != nil { + if err = WriteSoundMediaInfo(w, self.Sound); err != nil { + return + } + } + if self.Video != nil { + if err = WriteVideoMediaInfo(w, self.Video); err != nil { + return + } + } + if self.Sample != nil { + if err = WriteSampleTable(w, self.Sample); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type SoundMediaInfo struct { Version int @@ -374,7 +663,7 @@ type SoundMediaInfo struct { } func ReadSoundMediaInfo(r *io.LimitedReader) (res *SoundMediaInfo, err error) { - + log.Println("ReadSoundMediaInfo") self := &SoundMediaInfo{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -391,6 +680,30 @@ func ReadSoundMediaInfo(r *io.LimitedReader) (res *SoundMediaInfo, err error) { res = self return } +func WriteSoundMediaInfo(w io.WriteSeeker, self *SoundMediaInfo) (err error) { + log.Println("WriteSoundMediaInfo") + var aw *Writer + if aw, err = WriteAtomHeader(w, "smhd"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, self.Balance, 2); err != nil { + return + } + if err = WriteDummy(w, 2); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} type VideoMediaInfo struct { Version int @@ -400,7 +713,7 @@ type VideoMediaInfo struct { } func ReadVideoMediaInfo(r *io.LimitedReader) (res *VideoMediaInfo, err error) { - + log.Println("ReadVideoMediaInfo") self := &VideoMediaInfo{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -419,6 +732,32 @@ func ReadVideoMediaInfo(r *io.LimitedReader) (res *VideoMediaInfo, err error) { res = self return } +func WriteVideoMediaInfo(w io.WriteSeeker, self *VideoMediaInfo) (err error) { + log.Println("WriteVideoMediaInfo") + var aw *Writer + if aw, err = WriteAtomHeader(w, "vmhd"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, self.GraphicsMode, 2); err != nil { + return + } + for _, elem := range self.Opcolor { + if err = WriteInt(w, elem, 2); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type SampleTable struct { SampleDesc *SampleDesc @@ -431,9 +770,8 @@ type SampleTable struct { } func ReadSampleTable(r *io.LimitedReader) (res *SampleTable, err error) { - + log.Println("ReadSampleTable") self := &SampleTable{} - // ReadAtoms for r.N > 0 { var cc4 string var ar *io.LimitedReader @@ -483,7 +821,10 @@ func ReadSampleTable(r *io.LimitedReader) (res *SampleTable, err error) { return } } - + default: + { + log.Println("skip", cc4) + } } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -492,6 +833,53 @@ func ReadSampleTable(r *io.LimitedReader) (res *SampleTable, err error) { res = self return } +func WriteSampleTable(w io.WriteSeeker, self *SampleTable) (err error) { + log.Println("WriteSampleTable") + var aw *Writer + if aw, err = WriteAtomHeader(w, "stbl"); err != nil { + return + } + w = aw + if self.SampleDesc != nil { + if err = WriteSampleDesc(w, self.SampleDesc); err != nil { + return + } + } + if self.TimeToSample != nil { + if err = WriteTimeToSample(w, self.TimeToSample); err != nil { + return + } + } + if self.CompositionOffset != nil { + if err = WriteCompositionOffset(w, self.CompositionOffset); err != nil { + return + } + } + if self.SampleToChunk != nil { + if err = WriteSampleToChunk(w, self.SampleToChunk); err != nil { + return + } + } + if self.SyncSample != nil { + if err = WriteSyncSample(w, self.SyncSample); err != nil { + return + } + } + if self.ChunkOffset != nil { + if err = WriteChunkOffset(w, self.ChunkOffset); err != nil { + return + } + } + if self.SampleSize != nil { + if err = WriteSampleSize(w, self.SampleSize); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type SampleDesc struct { Version int @@ -500,7 +888,7 @@ type SampleDesc struct { } func ReadSampleDesc(r *io.LimitedReader) (res *SampleDesc, err error) { - + log.Println("ReadSampleDesc") self := &SampleDesc{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -521,6 +909,32 @@ func ReadSampleDesc(r *io.LimitedReader) (res *SampleDesc, err error) { res = self return } +func WriteSampleDesc(w io.WriteSeeker, self *SampleDesc) (err error) { + log.Println("WriteSampleDesc") + var aw *Writer + if aw, err = WriteAtomHeader(w, "stsd"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, len(self.Entries), 4); err != nil { + return + } + for _, elem := range self.Entries { + if err = WriteSampleDescEntry(w, elem); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type TimeToSample struct { Version int @@ -529,7 +943,7 @@ type TimeToSample struct { } func ReadTimeToSample(r *io.LimitedReader) (res *TimeToSample, err error) { - + log.Println("ReadTimeToSample") self := &TimeToSample{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -550,6 +964,32 @@ func ReadTimeToSample(r *io.LimitedReader) (res *TimeToSample, err error) { res = self return } +func WriteTimeToSample(w io.WriteSeeker, self *TimeToSample) (err error) { + log.Println("WriteTimeToSample") + var aw *Writer + if aw, err = WriteAtomHeader(w, "stts"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, len(self.Entries), 4); err != nil { + return + } + for _, elem := range self.Entries { + if err = WriteTimeToSampleEntry(w, elem); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type TimeToSampleEntry struct { Count int @@ -557,7 +997,7 @@ type TimeToSampleEntry struct { } func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err error) { - + log.Println("ReadTimeToSampleEntry") if self.Count, err = ReadInt(r, 4); err != nil { return } @@ -566,6 +1006,16 @@ func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err err } return } +func WriteTimeToSampleEntry(w io.WriteSeeker, self TimeToSampleEntry) (err error) { + log.Println("WriteTimeToSampleEntry") + if err = WriteInt(w, self.Count, 4); err != nil { + return + } + if err = WriteInt(w, self.Duration, 4); err != nil { + return + } + return +} type SampleToChunk struct { Version int @@ -574,7 +1024,7 @@ type SampleToChunk struct { } func ReadSampleToChunk(r *io.LimitedReader) (res *SampleToChunk, err error) { - + log.Println("ReadSampleToChunk") self := &SampleToChunk{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -595,6 +1045,32 @@ func ReadSampleToChunk(r *io.LimitedReader) (res *SampleToChunk, err error) { res = self return } +func WriteSampleToChunk(w io.WriteSeeker, self *SampleToChunk) (err error) { + log.Println("WriteSampleToChunk") + var aw *Writer + if aw, err = WriteAtomHeader(w, "stsc"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, len(self.Entries), 4); err != nil { + return + } + for _, elem := range self.Entries { + if err = WriteSampleToChunkEntry(w, elem); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type SampleToChunkEntry struct { FirstChunk int @@ -603,7 +1079,7 @@ type SampleToChunkEntry struct { } func ReadSampleToChunkEntry(r *io.LimitedReader) (self SampleToChunkEntry, err error) { - + log.Println("ReadSampleToChunkEntry") if self.FirstChunk, err = ReadInt(r, 4); err != nil { return } @@ -615,15 +1091,28 @@ func ReadSampleToChunkEntry(r *io.LimitedReader) (self SampleToChunkEntry, err e } return } +func WriteSampleToChunkEntry(w io.WriteSeeker, self SampleToChunkEntry) (err error) { + log.Println("WriteSampleToChunkEntry") + if err = WriteInt(w, self.FirstChunk, 4); err != nil { + return + } + if err = WriteInt(w, self.SamplesPerChunk, 4); err != nil { + return + } + if err = WriteInt(w, self.SampleDescId, 4); err != nil { + return + } + return +} type CompositionOffset struct { Version int Flags int - Entries []int + Entries []CompositionOffsetEntry } func ReadCompositionOffset(r *io.LimitedReader) (res *CompositionOffset, err error) { - + log.Println("ReadCompositionOffset") self := &CompositionOffset{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -635,15 +1124,41 @@ func ReadCompositionOffset(r *io.LimitedReader) (res *CompositionOffset, err err if count, err = ReadInt(r, 4); err != nil { return } - self.Entries = make([]int, count) + self.Entries = make([]CompositionOffsetEntry, count) for i := 0; i < count; i++ { - if self.Entries[i], err = ReadInt(r, 4); err != nil { + if self.Entries[i], err = ReadCompositionOffsetEntry(r); err != nil { return } } res = self return } +func WriteCompositionOffset(w io.WriteSeeker, self *CompositionOffset) (err error) { + log.Println("WriteCompositionOffset") + var aw *Writer + if aw, err = WriteAtomHeader(w, "ctts"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, len(self.Entries), 4); err != nil { + return + } + for _, elem := range self.Entries { + if err = WriteCompositionOffsetEntry(w, elem); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type CompositionOffsetEntry struct { Count int @@ -651,7 +1166,7 @@ type CompositionOffsetEntry struct { } func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntry, err error) { - + log.Println("ReadCompositionOffsetEntry") if self.Count, err = ReadInt(r, 4); err != nil { return } @@ -660,6 +1175,16 @@ func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntr } return } +func WriteCompositionOffsetEntry(w io.WriteSeeker, self CompositionOffsetEntry) (err error) { + log.Println("WriteCompositionOffsetEntry") + if err = WriteInt(w, self.Count, 4); err != nil { + return + } + if err = WriteInt(w, self.Offset, 4); err != nil { + return + } + return +} type SyncSample struct { Version int @@ -668,7 +1193,7 @@ type SyncSample struct { } func ReadSyncSample(r *io.LimitedReader) (res *SyncSample, err error) { - + log.Println("ReadSyncSample") self := &SyncSample{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -689,15 +1214,42 @@ func ReadSyncSample(r *io.LimitedReader) (res *SyncSample, err error) { res = self return } +func WriteSyncSample(w io.WriteSeeker, self *SyncSample) (err error) { + log.Println("WriteSyncSample") + var aw *Writer + if aw, err = WriteAtomHeader(w, "stss"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, len(self.Entries), 4); err != nil { + return + } + for _, elem := range self.Entries { + if err = WriteInt(w, elem, 4); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type SampleSize struct { - Version int - Flags int - Entries []int + Version int + Flags int + SampleSize int + Entries []int } func ReadSampleSize(r *io.LimitedReader) (res *SampleSize, err error) { - + log.Println("ReadSampleSize") self := &SampleSize{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -705,6 +1257,9 @@ func ReadSampleSize(r *io.LimitedReader) (res *SampleSize, err error) { if self.Flags, err = ReadInt(r, 3); err != nil { return } + if self.SampleSize, err = ReadInt(r, 4); err != nil { + return + } var count int if count, err = ReadInt(r, 4); err != nil { return @@ -718,6 +1273,35 @@ func ReadSampleSize(r *io.LimitedReader) (res *SampleSize, err error) { res = self return } +func WriteSampleSize(w io.WriteSeeker, self *SampleSize) (err error) { + log.Println("WriteSampleSize") + var aw *Writer + if aw, err = WriteAtomHeader(w, "stsz"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, self.SampleSize, 4); err != nil { + return + } + if err = WriteInt(w, len(self.Entries), 4); err != nil { + return + } + for _, elem := range self.Entries { + if err = WriteInt(w, elem, 4); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} type ChunkOffset struct { Version int @@ -726,7 +1310,7 @@ type ChunkOffset struct { } func ReadChunkOffset(r *io.LimitedReader) (res *ChunkOffset, err error) { - + log.Println("ReadChunkOffset") self := &ChunkOffset{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -747,3 +1331,29 @@ func ReadChunkOffset(r *io.LimitedReader) (res *ChunkOffset, err error) { res = self return } +func WriteChunkOffset(w io.WriteSeeker, self *ChunkOffset) (err error) { + log.Println("WriteChunkOffset") + var aw *Writer + if aw, err = WriteAtomHeader(w, "stco"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, len(self.Entries), 4); err != nil { + return + } + for _, elem := range self.Entries { + if err = WriteInt(w, elem, 4); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} diff --git a/atom/writer.go b/atom/writer.go new file mode 100644 index 0000000..325df54 --- /dev/null +++ b/atom/writer.go @@ -0,0 +1,80 @@ + +package atom + +import ( + "io" + "log" +) + +func WriteBytes(w io.Writer, b []byte) (err error) { + _, err = w.Write(b) + return +} + +func WriteUInt(w io.Writer, val uint, n int) (err error) { + var b [8]byte + for i := n-1; i >= 0; i-- { + b[i] = byte(val) + val >>= 8 + } + return WriteBytes(w, b[0:n]) +} + +func WriteInt(w io.Writer, val int, n int) (err error) { + return WriteUInt(w, uint(val), n) +} + +func WriteTimeStamp(w io.Writer, ts TimeStamp, n int) (err error) { + return WriteUInt(w, uint(ts), n) +} + +func WriteString(w io.Writer, val string) (err error) { + return WriteBytes(w, []byte(val)) +} + +func WriteDummy(w io.Writer, n int) (err error) { + return WriteBytes(w, make([]byte, n)) +} + +type Writer struct { + io.WriteSeeker + sizePos int64 +} + +func (self *Writer) Close() (err error) { + var curPos int64 + if curPos, err = self.Seek(0, 1); err != nil { + return + } + if _, err = self.Seek(self.sizePos, 0); err != nil { + return + } + if err = WriteInt(self, int(curPos - self.sizePos), 4); err != nil { + return + } + if _, err = self.Seek(curPos, 0); err != nil { + return + } + if false { + log.Println("writeback", self.sizePos, curPos, curPos-self.sizePos) + } + return +} + +func WriteAtomHeader(w io.WriteSeeker, cc4 string) (res *Writer, err error) { + self := &Writer{WriteSeeker: w} + + if self.sizePos, err = w.Seek(0, 1); err != nil { + return + } + if err = WriteDummy(self, 4); err != nil { + return + } + if err = WriteString(self, cc4); err != nil { + return + } + + res = self + return +} + diff --git a/mp4.go b/mp4.go index 73bfb3d..b26cf05 100644 --- a/mp4.go +++ b/mp4.go @@ -45,24 +45,47 @@ func Open(filename string) (file *File, err error) { lr := &io.LimitedReader{R: osfile, N: finfo.Size()} - for lr.N > 0 { - var r *io.LimitedReader - var cc4 string - if r, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { - return - } - if cc4 == "moov" { - var moov *atom.Movie - if moov, err = atom.ReadMovie(r); err != nil { - return - } - log.Println("tracks nr", len(moov.Tracks)) - } - log.Println("atom", cc4, "left", lr.N) - atom.ReadDummy(r, int(r.N)) + var outfile *os.File + if outfile, err = os.Create(filename+".out.mp4"); err != nil { + return } - if _, err = os.Create(filename+".out.mp4"); err != nil { + for lr.N > 0 { + var ar *io.LimitedReader + + var cc4 string + if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { + return + } + + if cc4 == "moov" { + var moov *atom.Movie + if moov, err = atom.ReadMovie(ar); err != nil { + return + } + log.Println("regen moov", "tracks nr", len(moov.Tracks)) + if err = atom.WriteMovie(outfile, moov); err != nil { + return + } + } else { + var aw *atom.Writer + if aw, err = atom.WriteAtomHeader(outfile, cc4); err != nil { + return + } + log.Println("copy", cc4) + if _, err = io.CopyN(aw, ar, ar.N); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + } + + //log.Println("atom", cc4, "left", lr.N) + //atom.ReadDummy(ar, int(ar.N)) + } + + if err = outfile.Close(); err != nil { return } From b810c9a2fbd7c0682b6b00ae129be767655153ae Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 21 Nov 2015 20:32:57 +0800 Subject: [PATCH 05/93] insert free atom after moov make vlc play --- example/read.go | 2 +- mp4.go | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/example/read.go b/example/read.go index 56d07c1..f6e0a14 100644 --- a/example/read.go +++ b/example/read.go @@ -7,7 +7,7 @@ import ( ) func main() { - if _, err := mp4.Open("tiny2-avconv.mp4"); err != nil { + if _, err := mp4.Open("mid.mp4"); err != nil { log.Println(err) return } diff --git a/mp4.go b/mp4.go index b26cf05..b3f1254 100644 --- a/mp4.go +++ b/mp4.go @@ -59,14 +59,26 @@ func Open(filename string) (file *File, err error) { } if cc4 == "moov" { + curPos, _ := outfile.Seek(0, 1) + origSize := ar.N+8 var moov *atom.Movie if moov, err = atom.ReadMovie(ar); err != nil { return } - log.Println("regen moov", "tracks nr", len(moov.Tracks)) if err = atom.WriteMovie(outfile, moov); err != nil { return } + curPosAfterRead, _ := outfile.Seek(0, 1) + bytesWritten := curPosAfterRead - curPos + + log.Println("regen moov", "tracks nr", len(moov.Tracks), + "origSize", origSize, "bytesWritten", bytesWritten, + ) + + padSize := origSize - bytesWritten - 8 + aw, _ := atom.WriteAtomHeader(outfile, "free") + atom.WriteDummy(outfile, int(padSize)) + aw.Close() } else { var aw *atom.Writer if aw, err = atom.WriteAtomHeader(outfile, cc4); err != nil { From 9ec3f04c16279a8164ac59437eb1ccf695bde956 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 22 Nov 2015 07:32:05 +0800 Subject: [PATCH 06/93] change some struct fields --- atom/genStruct.js | 54 ++++++--- atom/otherStruct.go | 65 ++++++++++- atom/reader.go | 7 +- atom/struct.go | 279 +++++++++++++++++++++++++++++--------------- atom/writer.go | 13 ++- mp4.go | 26 +++++ 6 files changed, 327 insertions(+), 117 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index 61630c0..74b0371 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -24,8 +24,8 @@ var atoms = { fields: [ ['version', 'int8'], ['flags', 'int24'], - ['cTime', 'TimeStamp32'], - ['mTime', 'TimeStamp32'], + ['createTime', 'TimeStamp32'], + ['modifyTime', 'TimeStamp32'], ['timeScale', 'TimeStamp32'], ['duration', 'TimeStamp32'], ['preferredRate', 'int32'], @@ -55,8 +55,8 @@ var atoms = { fields: [ ['version', 'int8'], ['flags', 'int24'], - ['cTime', 'TimeStamp32'], - ['mTime', 'TimeStamp32'], + ['createTime', 'TimeStamp32'], + ['modifyTime', 'TimeStamp32'], ['trackId', 'TimeStamp32'], ['_', '[4]byte'], ['duration', 'TimeStamp32'], @@ -66,8 +66,8 @@ var atoms = { ['volume', 'int16'], ['_', '[2]byte'], ['matrix', '[9]int32'], - ['trackWidth', 'int32'], - ['trackHeader', 'int32'], + ['trackWidth', 'Fixed32'], + ['trackHeight', 'Fixed32'], ], }, @@ -89,8 +89,8 @@ var atoms = { fields: [ ['version', 'int8'], ['flags', 'int24'], - ['cTime', 'int32'], - ['mTime', 'int32'], + ['createTime', 'int32'], + ['modifyTime', 'int32'], ['timeScale', 'int32'], ['duration', 'int32'], ['language', 'int16'], @@ -226,6 +226,25 @@ var atoms = { ], }, + videoSampleDescHeader: { + fields: [ + ['version', 'int16'], + ['revision', 'int16'], + ['vendor', 'int32'], + ['temporalQuality', 'int32'], + ['spatialQuality', 'int32'], + ['width', 'int16'], + ['height', 'int16'], + ['horizontalResolution', 'Fixed32'], + ['vorizontalResolution', 'Fixed32'], + ['_', 'int32'], + ['compressorName', '[32]char'], + ['frameCount', 'int16'], + ['depth', 'int16'], + ['colorTableId', 'int16'], + ], + }, + }; var DeclReadFunc = (opts) => { @@ -386,7 +405,7 @@ D('DeclStruct', 'name', 'body'); D('StrStmt', 'content'); D('Switch', 'cond', 'cases', 'default'); -var showlog = true; +var showlog = false; var S = s => s && s || ''; var dumpFn = f => { @@ -439,6 +458,10 @@ var dumpStmts = stmts => { var parseType = s => { var r = {}; var bracket = /^\[(.*)\]/; + var lenDiv = 8; + var types = /^(int|TimeStamp|byte|Fixed|char)/; + var number = /[0-9]+/; + if (s.match(bracket)) { var count = s.match(bracket)[1]; if (count.substr(0,3) == 'int') { @@ -449,31 +472,36 @@ var parseType = s => { r.arr = true; s = s.replace(bracket, ''); } + if (s.substr(0,1) == '*') { r.ptr = true; s = s.slice(1); } - var types = /^(int|TimeStamp|byte|cc)/; + if (s.match(types)) { r.type = s.match(types)[0]; r.fn = uc(r.type); s = s.replace(types, ''); } + if (r.type == 'byte' && r.arr) { r.len = r.count; r.fn = 'Bytes'; } - var lenDiv = 8; - if (r.type == 'cc') { + + if (r.type == 'char' && r.arr) { + r.len = r.count; r.fn = 'String'; r.type = 'string'; + r.arr = false; lenDiv = 1; } - var number = /[0-9]+/; + if (s.match(number)) { r.len = +s.match(number)[0]/lenDiv; s = s.replace(number, ''); } + if (s != '') { r.struct = s; r.Struct = uc(s); diff --git a/atom/otherStruct.go b/atom/otherStruct.go index 633fd28..5922f10 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -3,12 +3,58 @@ package atom import ( "io" + "bytes" + "log" + "encoding/hex" ) +type VideoSampleDesc struct { + VideoSampleDescHeader + AVCDecoderConf []byte +} + +func ReadVideoSampleDesc(r *io.LimitedReader) (res *VideoSampleDesc, err error) { + self := &VideoSampleDesc{} + + if self.VideoSampleDescHeader, err = ReadVideoSampleDescHeader(r); err != nil { + return + } + + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + + if false { + log.Println("VideoSampleDesc:", cc4, ar.N) + //log.Println("VideoSampleDesc:", "avcC", len(self.AVCDecoderConf)) + } + + switch cc4 { + case "avcC": { + if self.AVCDecoderConf, err = ReadBytes(ar, int(ar.N)); err != nil { + return + } + } + } + + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + + res = self + return +} + type SampleDescEntry struct { Format string DataRefIndex int Data []byte + + Video *VideoSampleDesc } func ReadSampleDescEntry(r *io.LimitedReader) (res *SampleDescEntry, err error) { @@ -22,9 +68,22 @@ func ReadSampleDescEntry(r *io.LimitedReader) (res *SampleDescEntry, err error) if self.DataRefIndex, err = ReadInt(r, 2); err != nil { return } + if self.Data, err = ReadBytes(r, int(r.N)); err != nil { return } + + if self.Format == "avc1" { + br := bytes.NewReader(self.Data) + var err error + self.Video, err = ReadVideoSampleDesc(&io.LimitedReader{R: br, N: int64(len(self.Data))}) + if false { + log.Println("ReadSampleDescEntry:", hex.Dump(self.Data)) + log.Println("ReadSampleDescEntry:", err) + } + } else if self.Format == "mp4a" { + } + res = self return } @@ -94,16 +153,16 @@ func WriteHandlerRefer(w io.WriteSeeker, self *HandlerRefer) (err error) { if err = WriteInt(w, self.Flags, 1); err != nil { return } - if err = WriteString(w, self.Type); err != nil { + if err = WriteString(w, self.Type, 4); err != nil { return } - if err = WriteString(w, self.SubType); err != nil { + if err = WriteString(w, self.SubType, 4); err != nil { return } if err = WriteDummy(w, 12); err != nil { return } - if err = WriteString(w, self.Name); err != nil { + if err = WriteString(w, self.Name, len(self.Name)); err != nil { return } if err = aw.Close(); err != nil { diff --git a/atom/reader.go b/atom/reader.go index ec6edcf..827e451 100644 --- a/atom/reader.go +++ b/atom/reader.go @@ -7,9 +7,6 @@ import ( "log" ) -type Fixed32 uint32 -type TimeStamp uint32 - func ReadBytes(r io.Reader, n int) (res []byte, err error) { res = make([]byte, n) if n, err = r.Read(res); err != nil { @@ -39,12 +36,12 @@ func ReadInt(r io.Reader, n int) (res int, err error) { return } -func ReadFixed32(r io.Reader, n int) (res Fixed32, err error) { +func ReadFixed(r io.Reader, n int) (res Fixed, err error) { var ui uint if ui, err = ReadUInt(r, n); err != nil { return } - res = Fixed32(ui) + res = Fixed(ui) return } diff --git a/atom/struct.go b/atom/struct.go index b167f7a..5fad356 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -3,21 +3,20 @@ package atom import ( "io" - "log" ) type FileType struct { } func ReadFileType(r *io.LimitedReader) (res *FileType, err error) { - log.Println("ReadFileType") + self := &FileType{} res = self return } func WriteFileType(w io.WriteSeeker, self *FileType) (err error) { - log.Println("WriteFileType") + var aw *Writer if aw, err = WriteAtomHeader(w, "ftyp"); err != nil { return @@ -36,7 +35,7 @@ type Movie struct { } func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { - log.Println("ReadMovie") + self := &Movie{} for r.N > 0 { var cc4 string @@ -59,10 +58,7 @@ func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { } self.Tracks = append(self.Tracks, item) } - default: - { - log.Println("skip", cc4) - } + } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -72,7 +68,7 @@ func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { return } func WriteMovie(w io.WriteSeeker, self *Movie) (err error) { - log.Println("WriteMovie") + var aw *Writer if aw, err = WriteAtomHeader(w, "moov"); err != nil { return @@ -99,8 +95,8 @@ func WriteMovie(w io.WriteSeeker, self *Movie) (err error) { type MovieHeader struct { Version int Flags int - CTime TimeStamp - MTime TimeStamp + CreateTime TimeStamp + ModifyTime TimeStamp TimeScale TimeStamp Duration TimeStamp PreferredRate int @@ -116,7 +112,7 @@ type MovieHeader struct { } func ReadMovieHeader(r *io.LimitedReader) (res *MovieHeader, err error) { - log.Println("ReadMovieHeader") + self := &MovieHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -124,10 +120,10 @@ func ReadMovieHeader(r *io.LimitedReader) (res *MovieHeader, err error) { if self.Flags, err = ReadInt(r, 3); err != nil { return } - if self.CTime, err = ReadTimeStamp(r, 4); err != nil { + if self.CreateTime, err = ReadTimeStamp(r, 4); err != nil { return } - if self.MTime, err = ReadTimeStamp(r, 4); err != nil { + if self.ModifyTime, err = ReadTimeStamp(r, 4); err != nil { return } if self.TimeScale, err = ReadTimeStamp(r, 4); err != nil { @@ -175,7 +171,7 @@ func ReadMovieHeader(r *io.LimitedReader) (res *MovieHeader, err error) { return } func WriteMovieHeader(w io.WriteSeeker, self *MovieHeader) (err error) { - log.Println("WriteMovieHeader") + var aw *Writer if aw, err = WriteAtomHeader(w, "mvhd"); err != nil { return @@ -187,10 +183,10 @@ func WriteMovieHeader(w io.WriteSeeker, self *MovieHeader) (err error) { if err = WriteInt(w, self.Flags, 3); err != nil { return } - if err = WriteTimeStamp(w, self.CTime, 4); err != nil { + if err = WriteTimeStamp(w, self.CreateTime, 4); err != nil { return } - if err = WriteTimeStamp(w, self.MTime, 4); err != nil { + if err = WriteTimeStamp(w, self.ModifyTime, 4); err != nil { return } if err = WriteTimeStamp(w, self.TimeScale, 4); err != nil { @@ -246,7 +242,7 @@ type Track struct { } func ReadTrack(r *io.LimitedReader) (res *Track, err error) { - log.Println("ReadTrack") + self := &Track{} for r.N > 0 { var cc4 string @@ -267,10 +263,7 @@ func ReadTrack(r *io.LimitedReader) (res *Track, err error) { return } } - default: - { - log.Println("skip", cc4) - } + } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -280,7 +273,7 @@ func ReadTrack(r *io.LimitedReader) (res *Track, err error) { return } func WriteTrack(w io.WriteSeeker, self *Track) (err error) { - log.Println("WriteTrack") + var aw *Writer if aw, err = WriteAtomHeader(w, "trak"); err != nil { return @@ -305,20 +298,20 @@ func WriteTrack(w io.WriteSeeker, self *Track) (err error) { type TrackHeader struct { Version int Flags int - CTime TimeStamp - MTime TimeStamp + CreateTime TimeStamp + ModifyTime TimeStamp TrackId TimeStamp Duration TimeStamp Layer int AlternateGroup int Volume int Matrix [9]int - TrackWidth int - TrackHeader int + TrackWidth Fixed + TrackHeight Fixed } func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { - log.Println("ReadTrackHeader") + self := &TrackHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -326,10 +319,10 @@ func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { if self.Flags, err = ReadInt(r, 3); err != nil { return } - if self.CTime, err = ReadTimeStamp(r, 4); err != nil { + if self.CreateTime, err = ReadTimeStamp(r, 4); err != nil { return } - if self.MTime, err = ReadTimeStamp(r, 4); err != nil { + if self.ModifyTime, err = ReadTimeStamp(r, 4); err != nil { return } if self.TrackId, err = ReadTimeStamp(r, 4); err != nil { @@ -361,17 +354,17 @@ func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { return } } - if self.TrackWidth, err = ReadInt(r, 4); err != nil { + if self.TrackWidth, err = ReadFixed(r, 4); err != nil { return } - if self.TrackHeader, err = ReadInt(r, 4); err != nil { + if self.TrackHeight, err = ReadFixed(r, 4); err != nil { return } res = self return } func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { - log.Println("WriteTrackHeader") + var aw *Writer if aw, err = WriteAtomHeader(w, "tkhd"); err != nil { return @@ -383,10 +376,10 @@ func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { if err = WriteInt(w, self.Flags, 3); err != nil { return } - if err = WriteTimeStamp(w, self.CTime, 4); err != nil { + if err = WriteTimeStamp(w, self.CreateTime, 4); err != nil { return } - if err = WriteTimeStamp(w, self.MTime, 4); err != nil { + if err = WriteTimeStamp(w, self.ModifyTime, 4); err != nil { return } if err = WriteTimeStamp(w, self.TrackId, 4); err != nil { @@ -418,10 +411,10 @@ func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { return } } - if err = WriteInt(w, self.TrackWidth, 4); err != nil { + if err = WriteFixed(w, self.TrackWidth, 4); err != nil { return } - if err = WriteInt(w, self.TrackHeader, 4); err != nil { + if err = WriteFixed(w, self.TrackHeight, 4); err != nil { return } if err = aw.Close(); err != nil { @@ -437,7 +430,7 @@ type Media struct { } func ReadMedia(r *io.LimitedReader) (res *Media, err error) { - log.Println("ReadMedia") + self := &Media{} for r.N > 0 { var cc4 string @@ -464,10 +457,7 @@ func ReadMedia(r *io.LimitedReader) (res *Media, err error) { return } } - default: - { - log.Println("skip", cc4) - } + } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -477,7 +467,7 @@ func ReadMedia(r *io.LimitedReader) (res *Media, err error) { return } func WriteMedia(w io.WriteSeeker, self *Media) (err error) { - log.Println("WriteMedia") + var aw *Writer if aw, err = WriteAtomHeader(w, "mdia"); err != nil { return @@ -505,18 +495,18 @@ func WriteMedia(w io.WriteSeeker, self *Media) (err error) { } type MediaHeader struct { - Version int - Flags int - CTime int - MTime int - TimeScale int - Duration int - Language int - Quality int + Version int + Flags int + CreateTime int + ModifyTime int + TimeScale int + Duration int + Language int + Quality int } func ReadMediaHeader(r *io.LimitedReader) (res *MediaHeader, err error) { - log.Println("ReadMediaHeader") + self := &MediaHeader{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -524,10 +514,10 @@ func ReadMediaHeader(r *io.LimitedReader) (res *MediaHeader, err error) { if self.Flags, err = ReadInt(r, 3); err != nil { return } - if self.CTime, err = ReadInt(r, 4); err != nil { + if self.CreateTime, err = ReadInt(r, 4); err != nil { return } - if self.MTime, err = ReadInt(r, 4); err != nil { + if self.ModifyTime, err = ReadInt(r, 4); err != nil { return } if self.TimeScale, err = ReadInt(r, 4); err != nil { @@ -546,7 +536,7 @@ func ReadMediaHeader(r *io.LimitedReader) (res *MediaHeader, err error) { return } func WriteMediaHeader(w io.WriteSeeker, self *MediaHeader) (err error) { - log.Println("WriteMediaHeader") + var aw *Writer if aw, err = WriteAtomHeader(w, "mdhd"); err != nil { return @@ -558,10 +548,10 @@ func WriteMediaHeader(w io.WriteSeeker, self *MediaHeader) (err error) { if err = WriteInt(w, self.Flags, 3); err != nil { return } - if err = WriteInt(w, self.CTime, 4); err != nil { + if err = WriteInt(w, self.CreateTime, 4); err != nil { return } - if err = WriteInt(w, self.MTime, 4); err != nil { + if err = WriteInt(w, self.ModifyTime, 4); err != nil { return } if err = WriteInt(w, self.TimeScale, 4); err != nil { @@ -589,7 +579,7 @@ type MediaInfo struct { } func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { - log.Println("ReadMediaInfo") + self := &MediaInfo{} for r.N > 0 { var cc4 string @@ -616,10 +606,7 @@ func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { return } } - default: - { - log.Println("skip", cc4) - } + } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -629,7 +616,7 @@ func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { return } func WriteMediaInfo(w io.WriteSeeker, self *MediaInfo) (err error) { - log.Println("WriteMediaInfo") + var aw *Writer if aw, err = WriteAtomHeader(w, "minf"); err != nil { return @@ -663,7 +650,7 @@ type SoundMediaInfo struct { } func ReadSoundMediaInfo(r *io.LimitedReader) (res *SoundMediaInfo, err error) { - log.Println("ReadSoundMediaInfo") + self := &SoundMediaInfo{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -681,7 +668,7 @@ func ReadSoundMediaInfo(r *io.LimitedReader) (res *SoundMediaInfo, err error) { return } func WriteSoundMediaInfo(w io.WriteSeeker, self *SoundMediaInfo) (err error) { - log.Println("WriteSoundMediaInfo") + var aw *Writer if aw, err = WriteAtomHeader(w, "smhd"); err != nil { return @@ -713,7 +700,7 @@ type VideoMediaInfo struct { } func ReadVideoMediaInfo(r *io.LimitedReader) (res *VideoMediaInfo, err error) { - log.Println("ReadVideoMediaInfo") + self := &VideoMediaInfo{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -733,7 +720,7 @@ func ReadVideoMediaInfo(r *io.LimitedReader) (res *VideoMediaInfo, err error) { return } func WriteVideoMediaInfo(w io.WriteSeeker, self *VideoMediaInfo) (err error) { - log.Println("WriteVideoMediaInfo") + var aw *Writer if aw, err = WriteAtomHeader(w, "vmhd"); err != nil { return @@ -770,7 +757,7 @@ type SampleTable struct { } func ReadSampleTable(r *io.LimitedReader) (res *SampleTable, err error) { - log.Println("ReadSampleTable") + self := &SampleTable{} for r.N > 0 { var cc4 string @@ -821,10 +808,7 @@ func ReadSampleTable(r *io.LimitedReader) (res *SampleTable, err error) { return } } - default: - { - log.Println("skip", cc4) - } + } if _, err = ReadDummy(ar, int(ar.N)); err != nil { return @@ -834,7 +818,7 @@ func ReadSampleTable(r *io.LimitedReader) (res *SampleTable, err error) { return } func WriteSampleTable(w io.WriteSeeker, self *SampleTable) (err error) { - log.Println("WriteSampleTable") + var aw *Writer if aw, err = WriteAtomHeader(w, "stbl"); err != nil { return @@ -888,7 +872,7 @@ type SampleDesc struct { } func ReadSampleDesc(r *io.LimitedReader) (res *SampleDesc, err error) { - log.Println("ReadSampleDesc") + self := &SampleDesc{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -910,7 +894,7 @@ func ReadSampleDesc(r *io.LimitedReader) (res *SampleDesc, err error) { return } func WriteSampleDesc(w io.WriteSeeker, self *SampleDesc) (err error) { - log.Println("WriteSampleDesc") + var aw *Writer if aw, err = WriteAtomHeader(w, "stsd"); err != nil { return @@ -943,7 +927,7 @@ type TimeToSample struct { } func ReadTimeToSample(r *io.LimitedReader) (res *TimeToSample, err error) { - log.Println("ReadTimeToSample") + self := &TimeToSample{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -965,7 +949,7 @@ func ReadTimeToSample(r *io.LimitedReader) (res *TimeToSample, err error) { return } func WriteTimeToSample(w io.WriteSeeker, self *TimeToSample) (err error) { - log.Println("WriteTimeToSample") + var aw *Writer if aw, err = WriteAtomHeader(w, "stts"); err != nil { return @@ -997,7 +981,7 @@ type TimeToSampleEntry struct { } func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err error) { - log.Println("ReadTimeToSampleEntry") + if self.Count, err = ReadInt(r, 4); err != nil { return } @@ -1007,7 +991,7 @@ func ReadTimeToSampleEntry(r *io.LimitedReader) (self TimeToSampleEntry, err err return } func WriteTimeToSampleEntry(w io.WriteSeeker, self TimeToSampleEntry) (err error) { - log.Println("WriteTimeToSampleEntry") + if err = WriteInt(w, self.Count, 4); err != nil { return } @@ -1024,7 +1008,7 @@ type SampleToChunk struct { } func ReadSampleToChunk(r *io.LimitedReader) (res *SampleToChunk, err error) { - log.Println("ReadSampleToChunk") + self := &SampleToChunk{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -1046,7 +1030,7 @@ func ReadSampleToChunk(r *io.LimitedReader) (res *SampleToChunk, err error) { return } func WriteSampleToChunk(w io.WriteSeeker, self *SampleToChunk) (err error) { - log.Println("WriteSampleToChunk") + var aw *Writer if aw, err = WriteAtomHeader(w, "stsc"); err != nil { return @@ -1079,7 +1063,7 @@ type SampleToChunkEntry struct { } func ReadSampleToChunkEntry(r *io.LimitedReader) (self SampleToChunkEntry, err error) { - log.Println("ReadSampleToChunkEntry") + if self.FirstChunk, err = ReadInt(r, 4); err != nil { return } @@ -1092,7 +1076,7 @@ func ReadSampleToChunkEntry(r *io.LimitedReader) (self SampleToChunkEntry, err e return } func WriteSampleToChunkEntry(w io.WriteSeeker, self SampleToChunkEntry) (err error) { - log.Println("WriteSampleToChunkEntry") + if err = WriteInt(w, self.FirstChunk, 4); err != nil { return } @@ -1112,7 +1096,7 @@ type CompositionOffset struct { } func ReadCompositionOffset(r *io.LimitedReader) (res *CompositionOffset, err error) { - log.Println("ReadCompositionOffset") + self := &CompositionOffset{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -1134,7 +1118,7 @@ func ReadCompositionOffset(r *io.LimitedReader) (res *CompositionOffset, err err return } func WriteCompositionOffset(w io.WriteSeeker, self *CompositionOffset) (err error) { - log.Println("WriteCompositionOffset") + var aw *Writer if aw, err = WriteAtomHeader(w, "ctts"); err != nil { return @@ -1166,7 +1150,7 @@ type CompositionOffsetEntry struct { } func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntry, err error) { - log.Println("ReadCompositionOffsetEntry") + if self.Count, err = ReadInt(r, 4); err != nil { return } @@ -1176,7 +1160,7 @@ func ReadCompositionOffsetEntry(r *io.LimitedReader) (self CompositionOffsetEntr return } func WriteCompositionOffsetEntry(w io.WriteSeeker, self CompositionOffsetEntry) (err error) { - log.Println("WriteCompositionOffsetEntry") + if err = WriteInt(w, self.Count, 4); err != nil { return } @@ -1193,7 +1177,7 @@ type SyncSample struct { } func ReadSyncSample(r *io.LimitedReader) (res *SyncSample, err error) { - log.Println("ReadSyncSample") + self := &SyncSample{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -1215,7 +1199,7 @@ func ReadSyncSample(r *io.LimitedReader) (res *SyncSample, err error) { return } func WriteSyncSample(w io.WriteSeeker, self *SyncSample) (err error) { - log.Println("WriteSyncSample") + var aw *Writer if aw, err = WriteAtomHeader(w, "stss"); err != nil { return @@ -1249,7 +1233,7 @@ type SampleSize struct { } func ReadSampleSize(r *io.LimitedReader) (res *SampleSize, err error) { - log.Println("ReadSampleSize") + self := &SampleSize{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -1274,7 +1258,7 @@ func ReadSampleSize(r *io.LimitedReader) (res *SampleSize, err error) { return } func WriteSampleSize(w io.WriteSeeker, self *SampleSize) (err error) { - log.Println("WriteSampleSize") + var aw *Writer if aw, err = WriteAtomHeader(w, "stsz"); err != nil { return @@ -1310,7 +1294,7 @@ type ChunkOffset struct { } func ReadChunkOffset(r *io.LimitedReader) (res *ChunkOffset, err error) { - log.Println("ReadChunkOffset") + self := &ChunkOffset{} if self.Version, err = ReadInt(r, 1); err != nil { return @@ -1332,7 +1316,7 @@ func ReadChunkOffset(r *io.LimitedReader) (res *ChunkOffset, err error) { return } func WriteChunkOffset(w io.WriteSeeker, self *ChunkOffset) (err error) { - log.Println("WriteChunkOffset") + var aw *Writer if aw, err = WriteAtomHeader(w, "stco"); err != nil { return @@ -1357,3 +1341,112 @@ func WriteChunkOffset(w io.WriteSeeker, self *ChunkOffset) (err error) { } return } + +type VideoSampleDescHeader struct { + Version int + Revision int + Vendor int + TemporalQuality int + SpatialQuality int + Width int + Height int + HorizontalResolution Fixed + VorizontalResolution Fixed + CompressorName string + FrameCount int + Depth int + ColorTableId int +} + +func ReadVideoSampleDescHeader(r *io.LimitedReader) (self VideoSampleDescHeader, err error) { + + if self.Version, err = ReadInt(r, 2); err != nil { + return + } + if self.Revision, err = ReadInt(r, 2); err != nil { + return + } + if self.Vendor, err = ReadInt(r, 4); err != nil { + return + } + if self.TemporalQuality, err = ReadInt(r, 4); err != nil { + return + } + if self.SpatialQuality, err = ReadInt(r, 4); err != nil { + return + } + if self.Width, err = ReadInt(r, 2); err != nil { + return + } + if self.Height, err = ReadInt(r, 2); err != nil { + return + } + if self.HorizontalResolution, err = ReadFixed(r, 4); err != nil { + return + } + if self.VorizontalResolution, err = ReadFixed(r, 4); err != nil { + return + } + if _, err = ReadDummy(r, 4); err != nil { + return + } + if self.CompressorName, err = ReadString(r, 32); err != nil { + return + } + if self.FrameCount, err = ReadInt(r, 2); err != nil { + return + } + if self.Depth, err = ReadInt(r, 2); err != nil { + return + } + if self.ColorTableId, err = ReadInt(r, 2); err != nil { + return + } + return +} +func WriteVideoSampleDescHeader(w io.WriteSeeker, self VideoSampleDescHeader) (err error) { + + if err = WriteInt(w, self.Version, 2); err != nil { + return + } + if err = WriteInt(w, self.Revision, 2); err != nil { + return + } + if err = WriteInt(w, self.Vendor, 4); err != nil { + return + } + if err = WriteInt(w, self.TemporalQuality, 4); err != nil { + return + } + if err = WriteInt(w, self.SpatialQuality, 4); err != nil { + return + } + if err = WriteInt(w, self.Width, 2); err != nil { + return + } + if err = WriteInt(w, self.Height, 2); err != nil { + return + } + if err = WriteFixed(w, self.HorizontalResolution, 4); err != nil { + return + } + if err = WriteFixed(w, self.VorizontalResolution, 4); err != nil { + return + } + if err = WriteDummy(w, 4); err != nil { + return + } + if err = WriteString(w, self.CompressorName, 32); err != nil { + return + } + if err = WriteInt(w, self.FrameCount, 2); err != nil { + return + } + if err = WriteInt(w, self.Depth, 2); err != nil { + return + } + if err = WriteInt(w, self.ColorTableId, 2); err != nil { + return + } + return +} diff --git a/atom/writer.go b/atom/writer.go index 325df54..a61b744 100644 --- a/atom/writer.go +++ b/atom/writer.go @@ -24,12 +24,19 @@ func WriteInt(w io.Writer, val int, n int) (err error) { return WriteUInt(w, uint(val), n) } +func WriteFixed(w io.Writer, val Fixed, n int) (err error) { + return WriteUInt(w, uint(val), n) +} + func WriteTimeStamp(w io.Writer, ts TimeStamp, n int) (err error) { return WriteUInt(w, uint(ts), n) } -func WriteString(w io.Writer, val string) (err error) { - return WriteBytes(w, []byte(val)) +func WriteString(w io.Writer, val string, n int) (err error) { + wb := make([]byte, n) + sb := []byte(val) + copy(wb, sb) + return WriteBytes(w, wb) } func WriteDummy(w io.Writer, n int) (err error) { @@ -70,7 +77,7 @@ func WriteAtomHeader(w io.WriteSeeker, cc4 string) (res *Writer, err error) { if err = WriteDummy(self, 4); err != nil { return } - if err = WriteString(self, cc4); err != nil { + if err = WriteString(self, cc4, 4); err != nil { return } diff --git a/mp4.go b/mp4.go index b3f1254..99f8325 100644 --- a/mp4.go +++ b/mp4.go @@ -31,6 +31,31 @@ func (self *File) Sync() { func (self *File) Close() { } +func changeMoov(moov *atom.Movie) { + header := moov.Header + + log.Println("moov: ", header.CreateTime, header.TimeScale, header.Duration) + log.Println("moov: ", header.PreferredRate, header.PreferredVolume) + log.Println("moov: ", header.PreviewTime, header.PreviewDuration) + log.Println("moov: ", header.PosterTime) + log.Println("moov: ", header.SelectionTime, header.SelectionDuration) + log.Println("moov: ", header.CurrentTime) + log.Println("moov: ", header.NextTrackId) + log.Println("moov: ", header.Matrix) + header.NextTrackId = 0 + + for i, track := range moov.Tracks { + log.Println("track", i, ":", track.Header.TrackId) + log.Println("track", i, ":", track.Header.Duration) + log.Println("track", i, ":", track.Header.Layer, track.Header.AlternateGroup) + log.Println("track", i, ":", track.Header.Volume) + log.Println("track", i, ":", track.Header.TrackWidth, track.Header.TrackHeight) + log.Println("track", i, ":", track.Header.Matrix) + + //media := track.Media + } +} + func Open(filename string) (file *File, err error) { var osfile *os.File if osfile, err = os.Open(filename); err != nil { @@ -65,6 +90,7 @@ func Open(filename string) (file *File, err error) { if moov, err = atom.ReadMovie(ar); err != nil { return } + changeMoov(moov) if err = atom.WriteMovie(outfile, moov); err != nil { return } From 706b6e107ba9f203ca89c76e5a421292de0c402d Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 22 Nov 2015 08:03:58 +0800 Subject: [PATCH 07/93] add types.go --- atom/types.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 atom/types.go diff --git a/atom/types.go b/atom/types.go new file mode 100644 index 0000000..56ca122 --- /dev/null +++ b/atom/types.go @@ -0,0 +1,14 @@ + +package atom + +type Fixed uint32 +type TimeStamp uint32 + +func IntToFixed(val int) Fixed { + return Fixed(val<<16) +} + +func FixedToInt(val Fixed) int { + return int(val>>16) +} + From f887bff6bd0041f96113bbd1c9e2cf5ca8d1bd42 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 22 Nov 2015 08:04:52 +0800 Subject: [PATCH 08/93] put $atoms into fields --- atom/genStruct.js | 107 ++++++++++++++++++++++++++-------------------- atom/struct.go | 24 ----------- 2 files changed, 60 insertions(+), 71 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index 74b0371..8e81ee8 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -5,17 +5,13 @@ Array.prototype.nonull = function () { }; var atoms = { - fileType: { - cc4: 'ftyp', - fields: [ - ], - }, - movie: { cc4: 'moov', - atoms: [ - ['header', '*movieHeader'], - ['tracks', '[]*track'], + fields: [ + ['$atoms', [ + ['header', '*movieHeader'], + ['tracks', '[]*track'], + ]], ], }, @@ -44,9 +40,11 @@ var atoms = { track: { cc4: 'trak', - atoms: [ - ['header', '*trackHeader'], - ['media', '*media'], + fields: [ + ['$atoms', [ + ['header', '*trackHeader'], + ['media', '*media'], + ]], ], }, @@ -77,10 +75,12 @@ var atoms = { media: { cc4: 'mdia', - atoms: [ - ['header', '*mediaHeader'], - ['info', '*mediaInfo'], - ['hdlr', '*handlerRefer'], + fields: [ + ['$atoms', [ + ['header', '*mediaHeader'], + ['info', '*mediaInfo'], + ['hdlr', '*handlerRefer'], + ]], ], }, @@ -100,10 +100,12 @@ var atoms = { mediaInfo: { cc4: 'minf', - atoms: [ - ['sound', '*soundMediaInfo'], - ['video', '*videoMediaInfo'], - ['sample', '*sampleTable'], + fields: [ + ['$atoms', [ + ['sound', '*soundMediaInfo'], + ['video', '*videoMediaInfo'], + ['sample', '*sampleTable'], + ]], ], }, @@ -129,14 +131,16 @@ var atoms = { sampleTable: { cc4: 'stbl', - atoms: [ - ['sampleDesc', '*sampleDesc'], - ['timeToSample', '*timeToSample'], - ['compositionOffset', '*compositionOffset'], - ['sampleToChunk', '*sampleToChunk'], - ['syncSample', '*syncSample'], - ['chunkOffset', '*chunkOffset'], - ['sampleSize', '*sampleSize'], + fields: [ + ['$atoms', [ + ['sampleDesc', '*sampleDesc'], + ['timeToSample', '*timeToSample'], + ['compositionOffset', '*compositionOffset'], + ['sampleToChunk', '*sampleToChunk'], + ['syncSample', '*syncSample'], + ['chunkOffset', '*chunkOffset'], + ['sampleSize', '*sampleSize'], + ]], ], }, @@ -268,12 +272,12 @@ var DeclReadFunc = (opts) => { }; var elemTypeStr = type => typeStr(Object.assign({}, type, {arr: false})); - var ReadAtoms = () => [ + var ReadAtoms = fields => [ For(`r.N > 0`, [ DeclVar('cc4', 'string'), DeclVar('ar', '*io.LimitedReader'), CallCheckAssign('ReadAtomHeader', ['r', '""'], ['ar', 'cc4']), - Switch('cc4', opts.fields.map(field => [ + Switch('cc4', fields.map(field => [ `"${atoms[field.type.struct].cc4}"`, [ field.type.arr ? [ DeclVar('item', elemTypeStr(field.type)), @@ -302,6 +306,8 @@ var DeclReadFunc = (opts) => { var ReadField = (name, type) => { if (name == '_') return CallCheckAssign('ReadDummy', ['r', type.len], ['_']); + if (name == '$atoms') + return ReadAtoms(type.list); if (type.arr && type.fn != 'Bytes') return ReadArr('self.'+name, type); return ReadCommnType('self.'+name, type); @@ -321,7 +327,7 @@ var DeclReadFunc = (opts) => { [[ptr?'res':'self', (ptr?'*':'')+opts.type], ['err', 'error']], [ ptr && `self := &${opts.type}{}`, - !opts.atoms ? ReadFields() : ReadAtoms(), + ReadFields(), ptr && `res = self`, ] ); @@ -338,7 +344,7 @@ var DeclWriteFunc = (opts) => { CallCheckAssign('aw.Close', [], []), ]; - var WriteAtoms = () => opts.fields.map(field => { + var WriteAtoms = fields => fields.map(field => { var name = 'self.'+field.name; return [ `if ${name} != nil {`, @@ -369,6 +375,8 @@ var DeclWriteFunc = (opts) => { var WriteField = (name, type) => { if (name == '_') return CallCheckAssign('WriteDummy', ['w', type.len], []); + if (name == '$atoms') + return WriteAtoms(type.list); if (type.arr && type.fn != 'Bytes') return WriteArr('self.'+name, type); return WriteCommnType('self.'+name, type); @@ -382,7 +390,7 @@ var DeclWriteFunc = (opts) => { [['err', 'error']], [ opts.cc4 && SavePos, - opts.atoms ? WriteAtoms() : WriteFields(), + WriteFields(), opts.cc4 && RestorePosSetSize, ] ); @@ -527,39 +535,44 @@ var nameShouldHide = (name) => name == '_' var allStmts = () => { var stmts = []; + var parseFields = fields => fields.map(field => { + return { + name: uc(field[0]), + type: field[0] == '$atoms' ? {list: parseFields(field[1])} : parseType(field[1]), + }; + }); + + var genStructFields = fields => fields.map(field => { + if (field.name == '_') + return; + if (field.name == '$atoms') + return field.type.list; + return [field]; + }).nonull().reduce((prev, cur) => prev.concat(cur)).map(field => [ + field.name, typeStr(field.type)]); + for (var k in atoms) { var atom = atoms[k]; var name = uc(k); - var fields = (atom.fields || atom.atoms); - if (fields == null) + if (atom.fields == null) continue; - fields = fields.map(field => { - return { - name: uc(field[0]), - type: parseType(field[1]), - }; - }); + var fields = parseFields(atom.fields); stmts = stmts.concat([ - DeclStruct(name, fields.map(field => !nameShouldHide(field.name) && [ - uc(field.name), - typeStr(field.type), - ]).nonull()), + DeclStruct(name, genStructFields(fields)), DeclReadFunc({ type: name, fields: fields, cc4: atom.cc4, - atoms: atom.atoms != null, }), DeclWriteFunc({ type: name, fields: fields, cc4: atom.cc4, - atoms: atom.atoms != null, }), ]); } diff --git a/atom/struct.go b/atom/struct.go index 5fad356..a7f4286 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -5,30 +5,6 @@ import ( "io" ) -type FileType struct { -} - -func ReadFileType(r *io.LimitedReader) (res *FileType, err error) { - - self := &FileType{} - - res = self - return -} -func WriteFileType(w io.WriteSeeker, self *FileType) (err error) { - - var aw *Writer - if aw, err = WriteAtomHeader(w, "ftyp"); err != nil { - return - } - w = aw - - if err = aw.Close(); err != nil { - return - } - return -} - type Movie struct { Header *MovieHeader Tracks []*Track From e5ed771fcda0411b1f59d98032cd07dae83d6647 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 22 Nov 2015 08:16:35 +0800 Subject: [PATCH 09/93] add WriteEmptyInt() and RefillInt() --- atom/writer.go | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/atom/writer.go b/atom/writer.go index a61b744..367c1a0 100644 --- a/atom/writer.go +++ b/atom/writer.go @@ -48,18 +48,39 @@ type Writer struct { sizePos int64 } +func (self *Writer) WriteEmptyInt(n int) (pos int64, err error) { + if pos, err = self.Seek(0, 1); err != nil { + return + } + if err = WriteInt(self, 0, n); err != nil { + return + } + return +} + +func (self *Writer) RefillInt(pos int64, val int, n int) (err error) { + var curPos int64 + if curPos, err = self.Seek(0, 1); err != nil { + return + } + if _, err = self.Seek(pos, 0); err != nil { + return + } + if err = WriteInt(self, val, n); err != nil { + return + } + if _, err = self.Seek(curPos, 0); err != nil { + return + } + return +} + func (self *Writer) Close() (err error) { var curPos int64 if curPos, err = self.Seek(0, 1); err != nil { return } - if _, err = self.Seek(self.sizePos, 0); err != nil { - return - } - if err = WriteInt(self, int(curPos - self.sizePos), 4); err != nil { - return - } - if _, err = self.Seek(curPos, 0); err != nil { + if err = self.RefillInt(self.sizePos, int(curPos - self.sizePos), 4); err != nil { return } if false { @@ -71,10 +92,7 @@ func (self *Writer) Close() (err error) { func WriteAtomHeader(w io.WriteSeeker, cc4 string) (res *Writer, err error) { self := &Writer{WriteSeeker: w} - if self.sizePos, err = w.Seek(0, 1); err != nil { - return - } - if err = WriteDummy(self, 4); err != nil { + if self.sizePos, err = self.WriteEmptyInt(4); err != nil { return } if err = WriteString(self, cc4, 4); err != nil { From 8cbf203548fad7c621ea699978a000f465ac6e1b Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 22 Nov 2015 08:48:25 +0800 Subject: [PATCH 10/93] put HandlerRefer into struct.go --- atom/genStruct.js | 20 ++++++++- atom/otherStruct.go | 62 --------------------------- atom/struct.go | 100 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 64 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index 8e81ee8..e856fc6 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -5,6 +5,15 @@ Array.prototype.nonull = function () { }; var atoms = { + test: { + cc4: 'sbss', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['left', '[]char'], + ], + }, + movie: { cc4: 'moov', fields: [ @@ -71,6 +80,13 @@ var atoms = { handlerRefer: { cc4: 'hdlr', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['type', '[4]char'], + ['subType', '[4]char'], + ['name', '[]char'], + ], }, media: { @@ -299,7 +315,7 @@ var DeclReadFunc = (opts) => { return [ //DebugStmt(type), CallCheckAssign( - 'Read'+type.fn, ['r', type.len].nonull(), [name]), + 'Read'+type.fn, ['r', type.len||'int(r.N)'], [name]), ] }; @@ -368,7 +384,7 @@ var DeclWriteFunc = (opts) => { 'Write'+type.Struct, ['w', name], []); return [ CallCheckAssign( - 'Write'+type.fn, ['w', name, type.len].nonull(), []), + 'Write'+type.fn, ['w', name, type.len||`len(${name})`], []), ] }; diff --git a/atom/otherStruct.go b/atom/otherStruct.go index 5922f10..5a99686 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -109,65 +109,3 @@ func WriteSampleDescEntry(w io.WriteSeeker, self *SampleDescEntry) (err error) { return } -type HandlerRefer struct { - Version int - Flags int - Type string - SubType string - Name string -} - -func ReadHandlerRefer(r *io.LimitedReader) (res *HandlerRefer, err error) { - self := &HandlerRefer{} - if self.Version, err = ReadInt(r, 3); err != nil { - return - } - if self.Flags, err = ReadInt(r, 1); err != nil { - return - } - if self.Type, err = ReadString(r, 4); err != nil { - return - } - if self.SubType, err = ReadString(r, 4); err != nil { - return - } - if _, err = ReadDummy(r, 12); err != nil { - return - } - if self.Name, err = ReadString(r, int(r.N)); err != nil { - return - } - res = self - return -} - -func WriteHandlerRefer(w io.WriteSeeker, self *HandlerRefer) (err error) { - var aw *Writer - if aw, err = WriteAtomHeader(w, "hdlr"); err != nil { - return - } - w = aw - if err = WriteInt(w, self.Version, 3); err != nil { - return - } - if err = WriteInt(w, self.Flags, 1); err != nil { - return - } - if err = WriteString(w, self.Type, 4); err != nil { - return - } - if err = WriteString(w, self.SubType, 4); err != nil { - return - } - if err = WriteDummy(w, 12); err != nil { - return - } - if err = WriteString(w, self.Name, len(self.Name)); err != nil { - return - } - if err = aw.Close(); err != nil { - return - } - return -} - diff --git a/atom/struct.go b/atom/struct.go index a7f4286..118c815 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -5,6 +5,49 @@ import ( "io" ) +type Test struct { + Version int + Flags int + Left string +} + +func ReadTest(r *io.LimitedReader) (res *Test, err error) { + + self := &Test{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.Left, err = ReadString(r, int(r.N)); err != nil { + return + } + res = self + return +} +func WriteTest(w io.WriteSeeker, self *Test) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "sbss"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteString(w, self.Left, len(self.Left)); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} + type Movie struct { Header *MovieHeader Tracks []*Track @@ -399,6 +442,63 @@ func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { return } +type HandlerRefer struct { + Version int + Flags int + Type string + SubType string + Name string +} + +func ReadHandlerRefer(r *io.LimitedReader) (res *HandlerRefer, err error) { + + self := &HandlerRefer{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.Type, err = ReadString(r, 4); err != nil { + return + } + if self.SubType, err = ReadString(r, 4); err != nil { + return + } + if self.Name, err = ReadString(r, int(r.N)); err != nil { + return + } + res = self + return +} +func WriteHandlerRefer(w io.WriteSeeker, self *HandlerRefer) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "hdlr"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteString(w, self.Type, 4); err != nil { + return + } + if err = WriteString(w, self.SubType, 4); err != nil { + return + } + if err = WriteString(w, self.Name, len(self.Name)); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} + type Media struct { Header *MediaHeader Info *MediaInfo From 88c3dbc49fb823e578ee8a09839f1154bf4a0a3c Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 22 Nov 2015 09:08:17 +0800 Subject: [PATCH 11/93] add $atomsCount --- atom/genStruct.js | 42 +++++++++++++++++++++++++++++++++++- atom/struct.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++ atom/writer.go | 20 +++++++++--------- 3 files changed, 105 insertions(+), 11 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index e856fc6..95ad682 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -11,6 +11,13 @@ var atoms = { ['version', 'int8'], ['flags', 'int24'], ['left', '[]char'], + + ['$atomsCount', 'int32'], + + ['$atoms', [ + ['header', '*movieHeader'], + ['tracks', '[]*track'], + ]], ], }, @@ -324,6 +331,8 @@ var DeclReadFunc = (opts) => { return CallCheckAssign('ReadDummy', ['r', type.len], ['_']); if (name == '$atoms') return ReadAtoms(type.list); + if (name == '$atomsCount') + return CallCheckAssign('ReadDummy', ['r', type.len], ['_']); if (type.arr && type.fn != 'Bytes') return ReadArr('self.'+name, type); return ReadCommnType('self.'+name, type); @@ -365,6 +374,7 @@ var DeclWriteFunc = (opts) => { return [ `if ${name} != nil {`, field.type.arr ? WriteArr(name, field.type) : WriteCommnType(name, field.type), + atomsCount && `${atomsCount.name}++`, `}`, ]; }); @@ -388,17 +398,45 @@ var DeclWriteFunc = (opts) => { ] }; + var atomsCount; + + var WriteAtomsCountStart = (type) => { + atomsCount = { + name: 'atomsCount', + namePos: 'atomsCountPos', + type: type, + } + return [ + DeclVar(atomsCount.name, 'int'), + DeclVar(atomsCount.namePos, 'int64'), + CallCheckAssign('WriteEmptyInt', ['w', type.len], [atomsCount.namePos]), + ]; + }; + + var WriteAtomsCountEnd = (type) => { + return [ + CallCheckAssign('RefillInt', + ['w', atomsCount.namePos, atomsCount.name, atomsCount.type.len], + [] + ), + ]; + }; + var WriteField = (name, type) => { if (name == '_') return CallCheckAssign('WriteDummy', ['w', type.len], []); if (name == '$atoms') return WriteAtoms(type.list); + if (name == '$atomsCount') + return WriteAtomsCountStart(type); if (type.arr && type.fn != 'Bytes') return WriteArr('self.'+name, type); return WriteCommnType('self.'+name, type); }; - var WriteFields = () => opts.fields.map(field => WriteField(field.name, field.type)); + var WriteFields = () => opts.fields + .map(field => WriteField(field.name, field.type)) + .concat(atomsCount && WriteAtomsCountEnd()) return Func( 'Write'+opts.type, @@ -561,6 +599,8 @@ var allStmts = () => { var genStructFields = fields => fields.map(field => { if (field.name == '_') return; + if (field.name == '$atomsCount') + return; if (field.name == '$atoms') return field.type.list; return [field]; diff --git a/atom/struct.go b/atom/struct.go index 118c815..06189c0 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -9,6 +9,8 @@ type Test struct { Version int Flags int Left string + Header *MovieHeader + Tracks []*Track } func ReadTest(r *io.LimitedReader) (res *Test, err error) { @@ -23,6 +25,36 @@ func ReadTest(r *io.LimitedReader) (res *Test, err error) { if self.Left, err = ReadString(r, int(r.N)); err != nil { return } + if _, err = ReadDummy(r, 4); err != nil { + return + } + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "mvhd": + { + if self.Header, err = ReadMovieHeader(ar); err != nil { + return + } + } + case "trak": + { + var item *Track + if item, err = ReadTrack(ar); err != nil { + return + } + self.Tracks = append(self.Tracks, item) + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } res = self return } @@ -42,6 +74,28 @@ func WriteTest(w io.WriteSeeker, self *Test) (err error) { if err = WriteString(w, self.Left, len(self.Left)); err != nil { return } + var atomsCount int + var atomsCountPos int64 + if atomsCountPos, err = WriteEmptyInt(w, 4); err != nil { + return + } + if self.Header != nil { + if err = WriteMovieHeader(w, self.Header); err != nil { + return + } + atomsCount++ + } + if self.Tracks != nil { + for _, elem := range self.Tracks { + if err = WriteTrack(w, elem); err != nil { + return + } + } + atomsCount++ + } + if err = RefillInt(w, atomsCountPos, atomsCount, 4); err != nil { + return + } if err = aw.Close(); err != nil { return } diff --git a/atom/writer.go b/atom/writer.go index 367c1a0..f50ca06 100644 --- a/atom/writer.go +++ b/atom/writer.go @@ -48,28 +48,28 @@ type Writer struct { sizePos int64 } -func (self *Writer) WriteEmptyInt(n int) (pos int64, err error) { - if pos, err = self.Seek(0, 1); err != nil { +func WriteEmptyInt(w io.WriteSeeker, n int) (pos int64, err error) { + if pos, err = w.Seek(0, 1); err != nil { return } - if err = WriteInt(self, 0, n); err != nil { + if err = WriteInt(w, 0, n); err != nil { return } return } -func (self *Writer) RefillInt(pos int64, val int, n int) (err error) { +func RefillInt(w io.WriteSeeker, pos int64, val int, n int) (err error) { var curPos int64 - if curPos, err = self.Seek(0, 1); err != nil { + if curPos, err = w.Seek(0, 1); err != nil { return } - if _, err = self.Seek(pos, 0); err != nil { + if _, err = w.Seek(pos, 0); err != nil { return } - if err = WriteInt(self, val, n); err != nil { + if err = WriteInt(w, val, n); err != nil { return } - if _, err = self.Seek(curPos, 0); err != nil { + if _, err = w.Seek(curPos, 0); err != nil { return } return @@ -80,7 +80,7 @@ func (self *Writer) Close() (err error) { if curPos, err = self.Seek(0, 1); err != nil { return } - if err = self.RefillInt(self.sizePos, int(curPos - self.sizePos), 4); err != nil { + if err = RefillInt(self, self.sizePos, int(curPos - self.sizePos), 4); err != nil { return } if false { @@ -92,7 +92,7 @@ func (self *Writer) Close() (err error) { func WriteAtomHeader(w io.WriteSeeker, cc4 string) (res *Writer, err error) { self := &Writer{WriteSeeker: w} - if self.sizePos, err = self.WriteEmptyInt(4); err != nil { + if self.sizePos, err = WriteEmptyInt(w, 4); err != nil { return } if err = WriteString(self, cc4, 4); err != nil { From 3b4f49126db00f0647cd817a703118ce1367f3f0 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 22 Nov 2015 11:22:24 +0800 Subject: [PATCH 12/93] can extract avc1Conf --- atom/genStruct.js | 85 ++++---- atom/otherStruct.go | 70 +------ atom/struct.go | 477 ++++++++++++++++++++++++-------------------- atom/writer.go | 16 +- mp4.go | 24 ++- 5 files changed, 342 insertions(+), 330 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index 95ad682..082e2a7 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -5,22 +5,6 @@ Array.prototype.nonull = function () { }; var atoms = { - test: { - cc4: 'sbss', - fields: [ - ['version', 'int8'], - ['flags', 'int24'], - ['left', '[]char'], - - ['$atomsCount', 'int32'], - - ['$atoms', [ - ['header', '*movieHeader'], - ['tracks', '[]*track'], - ]], - ], - }, - movie: { cc4: 'moov', fields: [ @@ -171,8 +155,52 @@ var atoms = { cc4: 'stsd', fields: [ ['version', 'int8'], - ['flags', 'int24'], - ['entries', '[int32]*sampleDescEntry'], + ['_', '[3]byte'], + ['$atomsCount', 'int32'], + ['$atoms', [ + ['avc1Desc', '*avc1Desc'], + ['mp4aDesc', '*mp4aDesc'], + ]], + ], + }, + + mp4aDesc: { + cc4: 'mp4a', + fields: [ + ['data', '[]byte'], + ], + }, + + avc1Desc: { + cc4: 'avc1', + fields: [ + ['_', '[6]byte'], + ['dataRefIdx', 'int16'], + ['version', 'int16'], + ['revision', 'int16'], + ['vendor', 'int32'], + ['temporalQuality', 'int32'], + ['spatialQuality', 'int32'], + ['width', 'int16'], + ['height', 'int16'], + ['horizontalResolution', 'Fixed32'], + ['vorizontalResolution', 'Fixed32'], + ['_', 'int32'], + ['frameCount', 'int16'], + ['compressorName', '[32]char'], + ['depth', 'int16'], + ['colorTableId', 'int16'], + + ['$atoms', [ + ['conf', '*avc1Conf'], + ]], + ], + }, + + avc1Conf: { + cc4: 'avcC', + fields: [ + ['data', '[]byte'], ], }, @@ -253,25 +281,6 @@ var atoms = { ], }, - videoSampleDescHeader: { - fields: [ - ['version', 'int16'], - ['revision', 'int16'], - ['vendor', 'int32'], - ['temporalQuality', 'int32'], - ['spatialQuality', 'int32'], - ['width', 'int16'], - ['height', 'int16'], - ['horizontalResolution', 'Fixed32'], - ['vorizontalResolution', 'Fixed32'], - ['_', 'int32'], - ['compressorName', '[32]char'], - ['frameCount', 'int16'], - ['depth', 'int16'], - ['colorTableId', 'int16'], - ], - }, - }; var DeclReadFunc = (opts) => { @@ -522,7 +531,7 @@ var parseType = s => { var bracket = /^\[(.*)\]/; var lenDiv = 8; var types = /^(int|TimeStamp|byte|Fixed|char)/; - var number = /[0-9]+/; + var number = /^[0-9]+/; if (s.match(bracket)) { var count = s.match(bracket)[1]; diff --git a/atom/otherStruct.go b/atom/otherStruct.go index 5a99686..ba68d30 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -2,12 +2,13 @@ package atom import ( - "io" - "bytes" - "log" - "encoding/hex" + _"io" + _"bytes" + _"log" + _"encoding/hex" ) +/* type VideoSampleDesc struct { VideoSampleDescHeader AVCDecoderConf []byte @@ -48,64 +49,5 @@ func ReadVideoSampleDesc(r *io.LimitedReader) (res *VideoSampleDesc, err error) res = self return } - -type SampleDescEntry struct { - Format string - DataRefIndex int - Data []byte - - Video *VideoSampleDesc -} - -func ReadSampleDescEntry(r *io.LimitedReader) (res *SampleDescEntry, err error) { - self := &SampleDescEntry{} - if r, self.Format, err = ReadAtomHeader(r, ""); err != nil { - return - } - if _, err = ReadDummy(r, 6); err != nil { - return - } - if self.DataRefIndex, err = ReadInt(r, 2); err != nil { - return - } - - if self.Data, err = ReadBytes(r, int(r.N)); err != nil { - return - } - - if self.Format == "avc1" { - br := bytes.NewReader(self.Data) - var err error - self.Video, err = ReadVideoSampleDesc(&io.LimitedReader{R: br, N: int64(len(self.Data))}) - if false { - log.Println("ReadSampleDescEntry:", hex.Dump(self.Data)) - log.Println("ReadSampleDescEntry:", err) - } - } else if self.Format == "mp4a" { - } - - res = self - return -} - -func WriteSampleDescEntry(w io.WriteSeeker, self *SampleDescEntry) (err error) { - var aw *Writer - if aw, err = WriteAtomHeader(w, self.Format); err != nil { - return - } - w = aw - if err = WriteDummy(w, 6); err != nil { - return - } - if err = WriteInt(w, self.DataRefIndex, 2); err != nil { - return - } - if err = WriteBytes(w, self.Data); err != nil { - return - } - if err = aw.Close(); err != nil { - return - } - return -} +*/ diff --git a/atom/struct.go b/atom/struct.go index 06189c0..2d0083a 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -5,103 +5,6 @@ import ( "io" ) -type Test struct { - Version int - Flags int - Left string - Header *MovieHeader - Tracks []*Track -} - -func ReadTest(r *io.LimitedReader) (res *Test, err error) { - - self := &Test{} - if self.Version, err = ReadInt(r, 1); err != nil { - return - } - if self.Flags, err = ReadInt(r, 3); err != nil { - return - } - if self.Left, err = ReadString(r, int(r.N)); err != nil { - return - } - if _, err = ReadDummy(r, 4); err != nil { - return - } - for r.N > 0 { - var cc4 string - var ar *io.LimitedReader - if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { - return - } - switch cc4 { - case "mvhd": - { - if self.Header, err = ReadMovieHeader(ar); err != nil { - return - } - } - case "trak": - { - var item *Track - if item, err = ReadTrack(ar); err != nil { - return - } - self.Tracks = append(self.Tracks, item) - } - - } - if _, err = ReadDummy(ar, int(ar.N)); err != nil { - return - } - } - res = self - return -} -func WriteTest(w io.WriteSeeker, self *Test) (err error) { - - var aw *Writer - if aw, err = WriteAtomHeader(w, "sbss"); err != nil { - return - } - w = aw - if err = WriteInt(w, self.Version, 1); err != nil { - return - } - if err = WriteInt(w, self.Flags, 3); err != nil { - return - } - if err = WriteString(w, self.Left, len(self.Left)); err != nil { - return - } - var atomsCount int - var atomsCountPos int64 - if atomsCountPos, err = WriteEmptyInt(w, 4); err != nil { - return - } - if self.Header != nil { - if err = WriteMovieHeader(w, self.Header); err != nil { - return - } - atomsCount++ - } - if self.Tracks != nil { - for _, elem := range self.Tracks { - if err = WriteTrack(w, elem); err != nil { - return - } - } - atomsCount++ - } - if err = RefillInt(w, atomsCountPos, atomsCount, 4); err != nil { - return - } - if err = aw.Close(); err != nil { - return - } - return -} - type Movie struct { Header *MovieHeader Tracks []*Track @@ -996,9 +899,9 @@ func WriteSampleTable(w io.WriteSeeker, self *SampleTable) (err error) { } type SampleDesc struct { - Version int - Flags int - Entries []*SampleDescEntry + Version int + Avc1Desc *Avc1Desc + Mp4aDesc *Mp4aDesc } func ReadSampleDesc(r *io.LimitedReader) (res *SampleDesc, err error) { @@ -1007,16 +910,34 @@ func ReadSampleDesc(r *io.LimitedReader) (res *SampleDesc, err error) { if self.Version, err = ReadInt(r, 1); err != nil { return } - if self.Flags, err = ReadInt(r, 3); err != nil { + if _, err = ReadDummy(r, 3); err != nil { return } - var count int - if count, err = ReadInt(r, 4); err != nil { + if _, err = ReadDummy(r, 4); err != nil { return } - self.Entries = make([]*SampleDescEntry, count) - for i := 0; i < count; i++ { - if self.Entries[i], err = ReadSampleDescEntry(r); err != nil { + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "avc1": + { + if self.Avc1Desc, err = ReadAvc1Desc(ar); err != nil { + return + } + } + case "mp4a": + { + if self.Mp4aDesc, err = ReadMp4aDesc(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { return } } @@ -1033,16 +954,243 @@ func WriteSampleDesc(w io.WriteSeeker, self *SampleDesc) (err error) { if err = WriteInt(w, self.Version, 1); err != nil { return } - if err = WriteInt(w, self.Flags, 3); err != nil { + if err = WriteDummy(w, 3); err != nil { return } - if err = WriteInt(w, len(self.Entries), 4); err != nil { + var atomsCount int + var atomsCountPos int64 + if atomsCountPos, err = WriteEmptyInt(w, 4); err != nil { return } - for _, elem := range self.Entries { - if err = WriteSampleDescEntry(w, elem); err != nil { + if self.Avc1Desc != nil { + if err = WriteAvc1Desc(w, self.Avc1Desc); err != nil { return } + atomsCount++ + } + if self.Mp4aDesc != nil { + if err = WriteMp4aDesc(w, self.Mp4aDesc); err != nil { + return + } + atomsCount++ + } + if err = RefillInt(w, atomsCountPos, atomsCount, 4); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} + +type Mp4aDesc struct { + Data []byte +} + +func ReadMp4aDesc(r *io.LimitedReader) (res *Mp4aDesc, err error) { + + self := &Mp4aDesc{} + if self.Data, err = ReadBytes(r, int(r.N)); err != nil { + return + } + res = self + return +} +func WriteMp4aDesc(w io.WriteSeeker, self *Mp4aDesc) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "mp4a"); err != nil { + return + } + w = aw + if err = WriteBytes(w, self.Data, len(self.Data)); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} + +type Avc1Desc struct { + DataRefIdx int + Version int + Revision int + Vendor int + TemporalQuality int + SpatialQuality int + Width int + Height int + HorizontalResolution Fixed + VorizontalResolution Fixed + FrameCount int + CompressorName string + Depth int + ColorTableId int + Conf *Avc1Conf +} + +func ReadAvc1Desc(r *io.LimitedReader) (res *Avc1Desc, err error) { + + self := &Avc1Desc{} + if _, err = ReadDummy(r, 6); err != nil { + return + } + if self.DataRefIdx, err = ReadInt(r, 2); err != nil { + return + } + if self.Version, err = ReadInt(r, 2); err != nil { + return + } + if self.Revision, err = ReadInt(r, 2); err != nil { + return + } + if self.Vendor, err = ReadInt(r, 4); err != nil { + return + } + if self.TemporalQuality, err = ReadInt(r, 4); err != nil { + return + } + if self.SpatialQuality, err = ReadInt(r, 4); err != nil { + return + } + if self.Width, err = ReadInt(r, 2); err != nil { + return + } + if self.Height, err = ReadInt(r, 2); err != nil { + return + } + if self.HorizontalResolution, err = ReadFixed(r, 4); err != nil { + return + } + if self.VorizontalResolution, err = ReadFixed(r, 4); err != nil { + return + } + if _, err = ReadDummy(r, 4); err != nil { + return + } + if self.FrameCount, err = ReadInt(r, 2); err != nil { + return + } + if self.CompressorName, err = ReadString(r, 32); err != nil { + return + } + if self.Depth, err = ReadInt(r, 2); err != nil { + return + } + if self.ColorTableId, err = ReadInt(r, 2); err != nil { + return + } + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "avcC": + { + if self.Conf, err = ReadAvc1Conf(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + res = self + return +} +func WriteAvc1Desc(w io.WriteSeeker, self *Avc1Desc) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "avc1"); err != nil { + return + } + w = aw + if err = WriteDummy(w, 6); err != nil { + return + } + if err = WriteInt(w, self.DataRefIdx, 2); err != nil { + return + } + if err = WriteInt(w, self.Version, 2); err != nil { + return + } + if err = WriteInt(w, self.Revision, 2); err != nil { + return + } + if err = WriteInt(w, self.Vendor, 4); err != nil { + return + } + if err = WriteInt(w, self.TemporalQuality, 4); err != nil { + return + } + if err = WriteInt(w, self.SpatialQuality, 4); err != nil { + return + } + if err = WriteInt(w, self.Width, 2); err != nil { + return + } + if err = WriteInt(w, self.Height, 2); err != nil { + return + } + if err = WriteFixed(w, self.HorizontalResolution, 4); err != nil { + return + } + if err = WriteFixed(w, self.VorizontalResolution, 4); err != nil { + return + } + if err = WriteDummy(w, 4); err != nil { + return + } + if err = WriteInt(w, self.FrameCount, 2); err != nil { + return + } + if err = WriteString(w, self.CompressorName, 32); err != nil { + return + } + if err = WriteInt(w, self.Depth, 2); err != nil { + return + } + if err = WriteInt(w, self.ColorTableId, 2); err != nil { + return + } + if self.Conf != nil { + if err = WriteAvc1Conf(w, self.Conf); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} + +type Avc1Conf struct { + Data []byte +} + +func ReadAvc1Conf(r *io.LimitedReader) (res *Avc1Conf, err error) { + + self := &Avc1Conf{} + if self.Data, err = ReadBytes(r, int(r.N)); err != nil { + return + } + res = self + return +} +func WriteAvc1Conf(w io.WriteSeeker, self *Avc1Conf) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "avcC"); err != nil { + return + } + w = aw + if err = WriteBytes(w, self.Data, len(self.Data)); err != nil { + return } if err = aw.Close(); err != nil { return @@ -1471,112 +1619,3 @@ func WriteChunkOffset(w io.WriteSeeker, self *ChunkOffset) (err error) { } return } - -type VideoSampleDescHeader struct { - Version int - Revision int - Vendor int - TemporalQuality int - SpatialQuality int - Width int - Height int - HorizontalResolution Fixed - VorizontalResolution Fixed - CompressorName string - FrameCount int - Depth int - ColorTableId int -} - -func ReadVideoSampleDescHeader(r *io.LimitedReader) (self VideoSampleDescHeader, err error) { - - if self.Version, err = ReadInt(r, 2); err != nil { - return - } - if self.Revision, err = ReadInt(r, 2); err != nil { - return - } - if self.Vendor, err = ReadInt(r, 4); err != nil { - return - } - if self.TemporalQuality, err = ReadInt(r, 4); err != nil { - return - } - if self.SpatialQuality, err = ReadInt(r, 4); err != nil { - return - } - if self.Width, err = ReadInt(r, 2); err != nil { - return - } - if self.Height, err = ReadInt(r, 2); err != nil { - return - } - if self.HorizontalResolution, err = ReadFixed(r, 4); err != nil { - return - } - if self.VorizontalResolution, err = ReadFixed(r, 4); err != nil { - return - } - if _, err = ReadDummy(r, 4); err != nil { - return - } - if self.CompressorName, err = ReadString(r, 32); err != nil { - return - } - if self.FrameCount, err = ReadInt(r, 2); err != nil { - return - } - if self.Depth, err = ReadInt(r, 2); err != nil { - return - } - if self.ColorTableId, err = ReadInt(r, 2); err != nil { - return - } - return -} -func WriteVideoSampleDescHeader(w io.WriteSeeker, self VideoSampleDescHeader) (err error) { - - if err = WriteInt(w, self.Version, 2); err != nil { - return - } - if err = WriteInt(w, self.Revision, 2); err != nil { - return - } - if err = WriteInt(w, self.Vendor, 4); err != nil { - return - } - if err = WriteInt(w, self.TemporalQuality, 4); err != nil { - return - } - if err = WriteInt(w, self.SpatialQuality, 4); err != nil { - return - } - if err = WriteInt(w, self.Width, 2); err != nil { - return - } - if err = WriteInt(w, self.Height, 2); err != nil { - return - } - if err = WriteFixed(w, self.HorizontalResolution, 4); err != nil { - return - } - if err = WriteFixed(w, self.VorizontalResolution, 4); err != nil { - return - } - if err = WriteDummy(w, 4); err != nil { - return - } - if err = WriteString(w, self.CompressorName, 32); err != nil { - return - } - if err = WriteInt(w, self.FrameCount, 2); err != nil { - return - } - if err = WriteInt(w, self.Depth, 2); err != nil { - return - } - if err = WriteInt(w, self.ColorTableId, 2); err != nil { - return - } - return -} diff --git a/atom/writer.go b/atom/writer.go index f50ca06..09ad1c6 100644 --- a/atom/writer.go +++ b/atom/writer.go @@ -6,8 +6,11 @@ import ( "log" ) -func WriteBytes(w io.Writer, b []byte) (err error) { - _, err = w.Write(b) +func WriteBytes(w io.Writer, b []byte, n int) (err error) { + if len(b) < n { + b = append(b, make([]byte, n-len(b))...) + } + _, err = w.Write(b[:n]) return } @@ -17,7 +20,7 @@ func WriteUInt(w io.Writer, val uint, n int) (err error) { b[i] = byte(val) val >>= 8 } - return WriteBytes(w, b[0:n]) + return WriteBytes(w, b[:], n) } func WriteInt(w io.Writer, val int, n int) (err error) { @@ -33,14 +36,11 @@ func WriteTimeStamp(w io.Writer, ts TimeStamp, n int) (err error) { } func WriteString(w io.Writer, val string, n int) (err error) { - wb := make([]byte, n) - sb := []byte(val) - copy(wb, sb) - return WriteBytes(w, wb) + return WriteBytes(w, []byte(val), n) } func WriteDummy(w io.Writer, n int) (err error) { - return WriteBytes(w, make([]byte, n)) + return WriteBytes(w, []byte{}, n) } type Writer struct { diff --git a/mp4.go b/mp4.go index 99f8325..d96328e 100644 --- a/mp4.go +++ b/mp4.go @@ -6,6 +6,7 @@ import ( "os" "io" "log" + "encoding/hex" ) type File struct { @@ -52,7 +53,28 @@ func changeMoov(moov *atom.Movie) { log.Println("track", i, ":", track.Header.TrackWidth, track.Header.TrackHeight) log.Println("track", i, ":", track.Header.Matrix) - //media := track.Media + media := track.Media + + if minf := media.Info; minf != nil { + if sample := minf.Sample; sample != nil { + if desc := sample.SampleDesc; desc != nil { + if avc1Desc := desc.Avc1Desc; avc1Desc != nil { + if conf := avc1Desc.Conf; conf != nil { + log.Println("avc1", hex.Dump(conf.Data)) + } + } + + /* + if mp4a := desc.Mp4aDesc; mp4a != nil { + if conf := mp4a.Conf; conf != nil { + log.Println("mp4a", hex.Dump(conf.Data)) + } + } + */ + + } + } + } } } From 37fe5058b48cbc695e05c315a414c6c2ba594332 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 22 Nov 2015 11:53:36 +0800 Subject: [PATCH 13/93] can extract mp4aConf --- atom/genStruct.js | 20 ++++++++ atom/struct.go | 126 +++++++++++++++++++++++++++++++++++++++++++++- example/read.go | 12 +++-- mp4.go | 4 +- 4 files changed, 154 insertions(+), 8 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index 082e2a7..95354fb 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -167,6 +167,26 @@ var atoms = { mp4aDesc: { cc4: 'mp4a', fields: [ + ['_', '[6]byte'], + ['dataRefIdx', 'int16'], + ['version', 'int16'], + ['revisionLevel', 'int16'], + ['vendor', 'int32'], + ['numberOfChannels', 'int16'], + ['sampleSize', 'int16'], + ['compressionId', 'int16'], + ['_', 'int16'], + ['sampleRate', 'Fixed32'], + ['$atoms', [ + ['conf', '*elemStreamDesc'], + ]], + ], + }, + + elemStreamDesc: { + cc4: 'esds', + fields: [ + ['version', 'int32'], ['data', '[]byte'], ], }, diff --git a/atom/struct.go b/atom/struct.go index 2d0083a..548a2b7 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -984,15 +984,69 @@ func WriteSampleDesc(w io.WriteSeeker, self *SampleDesc) (err error) { } type Mp4aDesc struct { - Data []byte + DataRefIdx int + Version int + RevisionLevel int + Vendor int + NumberOfChannels int + SampleSize int + CompressionId int + SampleRate Fixed + Conf *ElemStreamDesc } func ReadMp4aDesc(r *io.LimitedReader) (res *Mp4aDesc, err error) { self := &Mp4aDesc{} - if self.Data, err = ReadBytes(r, int(r.N)); err != nil { + if _, err = ReadDummy(r, 6); err != nil { return } + if self.DataRefIdx, err = ReadInt(r, 2); err != nil { + return + } + if self.Version, err = ReadInt(r, 2); err != nil { + return + } + if self.RevisionLevel, err = ReadInt(r, 2); err != nil { + return + } + if self.Vendor, err = ReadInt(r, 4); err != nil { + return + } + if self.NumberOfChannels, err = ReadInt(r, 2); err != nil { + return + } + if self.SampleSize, err = ReadInt(r, 2); err != nil { + return + } + if self.CompressionId, err = ReadInt(r, 2); err != nil { + return + } + if _, err = ReadDummy(r, 2); err != nil { + return + } + if self.SampleRate, err = ReadFixed(r, 4); err != nil { + return + } + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "esds": + { + if self.Conf, err = ReadElemStreamDesc(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } res = self return } @@ -1003,6 +1057,74 @@ func WriteMp4aDesc(w io.WriteSeeker, self *Mp4aDesc) (err error) { return } w = aw + if err = WriteDummy(w, 6); err != nil { + return + } + if err = WriteInt(w, self.DataRefIdx, 2); err != nil { + return + } + if err = WriteInt(w, self.Version, 2); err != nil { + return + } + if err = WriteInt(w, self.RevisionLevel, 2); err != nil { + return + } + if err = WriteInt(w, self.Vendor, 4); err != nil { + return + } + if err = WriteInt(w, self.NumberOfChannels, 2); err != nil { + return + } + if err = WriteInt(w, self.SampleSize, 2); err != nil { + return + } + if err = WriteInt(w, self.CompressionId, 2); err != nil { + return + } + if err = WriteDummy(w, 2); err != nil { + return + } + if err = WriteFixed(w, self.SampleRate, 4); err != nil { + return + } + if self.Conf != nil { + if err = WriteElemStreamDesc(w, self.Conf); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} + +type ElemStreamDesc struct { + Version int + Data []byte +} + +func ReadElemStreamDesc(r *io.LimitedReader) (res *ElemStreamDesc, err error) { + + self := &ElemStreamDesc{} + if self.Version, err = ReadInt(r, 4); err != nil { + return + } + if self.Data, err = ReadBytes(r, int(r.N)); err != nil { + return + } + res = self + return +} +func WriteElemStreamDesc(w io.WriteSeeker, self *ElemStreamDesc) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "esds"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 4); err != nil { + return + } if err = WriteBytes(w, self.Data, len(self.Data)); err != nil { return } diff --git a/example/read.go b/example/read.go index f6e0a14..1cfa22a 100644 --- a/example/read.go +++ b/example/read.go @@ -3,13 +3,19 @@ package main import ( mp4 "./.." + "flag" "log" ) func main() { - if _, err := mp4.Open("mid.mp4"); err != nil { - log.Println(err) - return + testconv := flag.Bool("testconv", false, "") + flag.Parse() + + if *testconv { + if _, err := mp4.TestConvert(flag.Arg(0)); err != nil { + log.Println(err) + return + } } } diff --git a/mp4.go b/mp4.go index d96328e..cb14d41 100644 --- a/mp4.go +++ b/mp4.go @@ -64,13 +64,11 @@ func changeMoov(moov *atom.Movie) { } } - /* if mp4a := desc.Mp4aDesc; mp4a != nil { if conf := mp4a.Conf; conf != nil { log.Println("mp4a", hex.Dump(conf.Data)) } } - */ } } @@ -78,7 +76,7 @@ func changeMoov(moov *atom.Movie) { } } -func Open(filename string) (file *File, err error) { +func TestConvert(filename string) (file *File, err error) { var osfile *os.File if osfile, err = os.Open(filename); err != nil { return From 3f60565c66fb8654adb96bf7c5646dbbcedc7740 Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 24 Nov 2015 08:44:42 +0800 Subject: [PATCH 14/93] make vlc can play rewrite mp4 file --- atom/genStruct.js | 4 +- atom/reader.go | 10 +- atom/struct.go | 18 +-- atom/writer.go | 8 +- example/read.go | 9 ++ mp4.go | 369 +++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 386 insertions(+), 32 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index 95354fb..99628f8 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -55,7 +55,7 @@ var atoms = { ['flags', 'int24'], ['createTime', 'TimeStamp32'], ['modifyTime', 'TimeStamp32'], - ['trackId', 'TimeStamp32'], + ['trackId', 'int32'], ['_', '[4]byte'], ['duration', 'TimeStamp32'], ['_', '[8]byte'], @@ -86,7 +86,7 @@ var atoms = { ['$atoms', [ ['header', '*mediaHeader'], ['info', '*mediaInfo'], - ['hdlr', '*handlerRefer'], + ['handler', '*handlerRefer'], ]], ], }, diff --git a/atom/reader.go b/atom/reader.go index 827e451..28bd77d 100644 --- a/atom/reader.go +++ b/atom/reader.go @@ -28,11 +28,15 @@ func ReadUInt(r io.Reader, n int) (res uint, err error) { } func ReadInt(r io.Reader, n int) (res int, err error) { - var ui uint - if ui, err = ReadUInt(r, n); err != nil { + var uval uint + if uval, err = ReadUInt(r, n); err != nil { return } - res = int(ui) + if uval&(1< 0 { + var ar *io.LimitedReader + + var cc4 string + if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { + return + } + log.Println("cc4", cc4) + + if cc4 == "moov" { + if moov, err = atom.ReadMovie(ar); err != nil { + return + } + } else if cc4 == "mdat" { + mdatOffset, _ = infile.Seek(0, 1) + break + } + + if _, err = atom.ReadDummy(lr, int(ar.N)); err != nil { + return + } + } + + if mdatOffset == -1 { + log.Println("mdat not found") + return + } + + outfileName := filename+".out.mp4" + var outfile *os.File + if outfile, err = os.Create(outfileName); err != nil { + return + } + + if err = rewrite(moov, infile, outfile); err != nil { + return + } + + if err = outfile.Close(); err != nil { + return + } + log.Println("output file", outfileName, "saved") + + return +} + + func TestConvert(filename string) (file *File, err error) { var osfile *os.File if osfile, err = os.Open(filename); err != nil { @@ -104,6 +430,7 @@ func TestConvert(filename string) (file *File, err error) { } if cc4 == "moov" { + curPos, _ := outfile.Seek(0, 1) origSize := ar.N+8 var moov *atom.Movie @@ -125,9 +452,17 @@ func TestConvert(filename string) (file *File, err error) { aw, _ := atom.WriteAtomHeader(outfile, "free") atom.WriteDummy(outfile, int(padSize)) aw.Close() + } else { + + var outcc4 string + if cc4 != "mdat" { + outcc4 = "free" + } else { + outcc4 = "mdat" + } var aw *atom.Writer - if aw, err = atom.WriteAtomHeader(outfile, cc4); err != nil { + if aw, err = atom.WriteAtomHeader(outfile, outcc4); err != nil { return } log.Println("copy", cc4) From 2ba72a94e85890978f9851970257220bdc48e2ed Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 24 Nov 2015 16:25:40 +0800 Subject: [PATCH 15/93] Read/Write AVCDecoderConfRecord --- atom/genStruct.js | 2 +- atom/otherStruct.go | 137 +++++++++++++++++++++++++++++++++----------- atom/struct.go | 6 +- mp4.go | 13 +++-- 4 files changed, 117 insertions(+), 41 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index 99628f8..b8abe8e 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -220,7 +220,7 @@ var atoms = { avc1Conf: { cc4: 'avcC', fields: [ - ['data', '[]byte'], + ['record', 'AVCDecoderConfRecord'], ], }, diff --git a/atom/otherStruct.go b/atom/otherStruct.go index ba68d30..d384b42 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -2,52 +2,125 @@ package atom import ( - _"io" - _"bytes" - _"log" - _"encoding/hex" + "io" + "fmt" ) -/* -type VideoSampleDesc struct { - VideoSampleDescHeader - AVCDecoderConf []byte +type AVCDecoderConfRecord struct { + AVCProfileIndication int + ProfileCompatibility int + AVCLevelIndication int + LengthSizeMinusOne int + SeqenceParamSet [][]byte + PictureParamSet [][]byte } -func ReadVideoSampleDesc(r *io.LimitedReader) (res *VideoSampleDesc, err error) { - self := &VideoSampleDesc{} +func CreateAVCDecoderConfRecord( + SeqenceParamSet []byte, + PictureParamSet []byte, +) (self AVCDecoderConfRecord, err error) { + if len(SeqenceParamSet) < 4 { + err = fmt.Errorf("invalid SeqenceParamSet") + return + } + self.AVCProfileIndication = int(SeqenceParamSet[1]) + self.AVCLevelIndication = int(SeqenceParamSet[3]) + self.LengthSizeMinusOne = 3 + return +} - if self.VideoSampleDescHeader, err = ReadVideoSampleDescHeader(r); err != nil { +func WriteAVCDecoderConfRecord(w io.Writer, self AVCDecoderConfRecord) (err error) { + if err = WriteInt(w, 1, 1); err != nil { + return + } + if err = WriteInt(w, self.AVCProfileIndication, 1); err != nil { + return + } + if err = WriteInt(w, self.ProfileCompatibility, 1); err != nil { + return + } + if err = WriteInt(w, self.AVCLevelIndication, 1); err != nil { + return + } + if err = WriteInt(w, self.LengthSizeMinusOne | 0xfc, 1); err != nil { return } - for r.N > 0 { - var cc4 string - var ar *io.LimitedReader - if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + if err = WriteInt(w, len(self.SeqenceParamSet) | 0xe0, 1); err != nil { + return + } + for _, data := range self.SeqenceParamSet { + if err = WriteInt(w, len(data), 2); err != nil { return } - - if false { - log.Println("VideoSampleDesc:", cc4, ar.N) - //log.Println("VideoSampleDesc:", "avcC", len(self.AVCDecoderConf)) - } - - switch cc4 { - case "avcC": { - if self.AVCDecoderConf, err = ReadBytes(ar, int(ar.N)); err != nil { - return - } - } - } - - if _, err = ReadDummy(ar, int(ar.N)); err != nil { + if err = WriteBytes(w, data, len(data)); err != nil { + return + } + } + + if err = WriteInt(w, len(self.PictureParamSet), 1); err != nil { + return + } + for _, data := range self.PictureParamSet { + if err = WriteInt(w, len(data), 2); err != nil { + return + } + if err = WriteBytes(w, data, len(data)); err != nil { return } } - res = self return } -*/ + +func ReadAVCDecoderConfRecord(r *io.LimitedReader) (self AVCDecoderConfRecord, err error) { + if _, err = ReadDummy(r, 1); err != nil { + return + } + if self.AVCProfileIndication, err = ReadInt(r, 1); err != nil { + return + } + if self.ProfileCompatibility, err = ReadInt(r, 1); err != nil { + return + } + if self.AVCLevelIndication, err = ReadInt(r, 1); err != nil { + return + } + if self.LengthSizeMinusOne, err = ReadInt(r, 1); err != nil { + return + } + self.LengthSizeMinusOne &= 0x03 + + var n, length int + var data []byte + + if n, err = ReadInt(r, 1); err != nil { + return + } + n &= 0x1f + for i := 0; i < n; i++ { + if length, err = ReadInt(r, 2); err != nil { + return + } + if data, err = ReadBytes(r, length); err != nil { + return + } + self.SeqenceParamSet = append(self.SeqenceParamSet, data) + } + + if n, err = ReadInt(r, 1); err != nil { + return + } + for i := 0; i < n; i++ { + if length, err = ReadInt(r, 2); err != nil { + return + } + if data, err = ReadBytes(r, length); err != nil { + return + } + self.PictureParamSet = append(self.PictureParamSet, data) + } + + return +} diff --git a/atom/struct.go b/atom/struct.go index 8eb422f..6606be2 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -1292,13 +1292,13 @@ func WriteAvc1Desc(w io.WriteSeeker, self *Avc1Desc) (err error) { } type Avc1Conf struct { - Data []byte + Record AVCDecoderConfRecord } func ReadAvc1Conf(r *io.LimitedReader) (res *Avc1Conf, err error) { self := &Avc1Conf{} - if self.Data, err = ReadBytes(r, int(r.N)); err != nil { + if self.Record, err = ReadAVCDecoderConfRecord(r); err != nil { return } res = self @@ -1311,7 +1311,7 @@ func WriteAvc1Conf(w io.WriteSeeker, self *Avc1Conf) (err error) { return } w = aw - if err = WriteBytes(w, self.Data, len(self.Data)); err != nil { + if err = WriteAVCDecoderConfRecord(w, self.Record); err != nil { return } if err = aw.Close(); err != nil { diff --git a/mp4.go b/mp4.go index bf9e258..723f798 100644 --- a/mp4.go +++ b/mp4.go @@ -106,8 +106,10 @@ func changeMoov(moov *atom.Movie) { if avc1Desc := desc.Avc1Desc; avc1Desc != nil { if conf := avc1Desc.Conf; conf != nil { if true { - log.Println("avc1", hex.Dump(conf.Data)) - log.Println("avc1desc", avc1Desc) + //log.Println("avc1", hex.Dump(conf.Data)) + log.Println("avc1desc", conf) + //avcconf, _ := atom.ReadAVCDecoderConfRecord(bytes.NewReader(conf.Data)) + //log.Println("avcconf", avcconf) } } } @@ -225,6 +227,8 @@ func rewrite(moov *atom.Movie, mdat io.ReadSeeker, outfile io.WriteSeeker) (err sampleCh := make(chan Sample) go readSamples(vsample, mdat, sampleCh) + log.Println("avc1Desc.conf", vsample.SampleDesc.Avc1Desc.Conf) + newsample := &atom.SampleTable{ SampleDesc: &atom.SampleDesc{ Avc1Desc: &atom.Avc1Desc{ @@ -311,8 +315,8 @@ func rewrite(moov *atom.Movie, mdat io.ReadSeeker, outfile io.WriteSeeker) (err Duration: vtrack.Header.Duration, Volume: vtrack.Header.Volume, Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - TrackWidth: vtrack.Header.TrackWidth, - TrackHeight: vtrack.Header.TrackHeight, + //TrackWidth: vtrack.Header.TrackWidth, + //TrackHeight: vtrack.Header.TrackHeight, TrackId: 1, }, @@ -401,7 +405,6 @@ func TestRewrite(filename string) (file *File, err error) { return } - func TestConvert(filename string) (file *File, err error) { var osfile *os.File if osfile, err = os.Open(filename); err != nil { From 5b9bccba88c16799fb0fc79e6ed2fbfbf5973357 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 25 Nov 2015 00:01:26 +0800 Subject: [PATCH 16/93] add SimpleH264Writer --- atom/genStruct.js | 16 +- atom/otherStruct.go | 4 +- atom/reader.go | 10 +- atom/struct.go | 48 ++--- atom/writer.go | 12 +- avcc.go | 17 -- example/read.go | 4 +- mp4.go | 494 -------------------------------------------- mp4a.go | 15 -- writer.go | 156 ++++++++++++++ 10 files changed, 213 insertions(+), 563 deletions(-) delete mode 100644 avcc.go delete mode 100644 mp4.go delete mode 100644 mp4a.go create mode 100644 writer.go diff --git a/atom/genStruct.js b/atom/genStruct.js index b8abe8e..c62ac57 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -22,10 +22,10 @@ var atoms = { ['flags', 'int24'], ['createTime', 'TimeStamp32'], ['modifyTime', 'TimeStamp32'], - ['timeScale', 'TimeStamp32'], - ['duration', 'TimeStamp32'], - ['preferredRate', 'int32'], - ['preferredVolume', 'int16'], + ['timeScale', 'int32'], + ['duration', 'int32'], + ['preferredRate', 'Fixed32'], + ['preferredVolume', 'Fixed16'], ['_', '[10]byte'], ['matrix', '[9]int32'], ['previewTime', 'TimeStamp32'], @@ -57,11 +57,11 @@ var atoms = { ['modifyTime', 'TimeStamp32'], ['trackId', 'int32'], ['_', '[4]byte'], - ['duration', 'TimeStamp32'], + ['duration', 'int32'], ['_', '[8]byte'], ['layer', 'int16'], ['alternateGroup', 'int16'], - ['volume', 'int16'], + ['volume', 'Fixed16'], ['_', '[2]byte'], ['matrix', '[9]int32'], ['trackWidth', 'Fixed32'], @@ -96,8 +96,8 @@ var atoms = { fields: [ ['version', 'int8'], ['flags', 'int24'], - ['createTime', 'int32'], - ['modifyTime', 'int32'], + ['createTime', 'TimeStamp32'], + ['modifyTime', 'TimeStamp32'], ['timeScale', 'int32'], ['duration', 'int32'], ['language', 'int16'], diff --git a/atom/otherStruct.go b/atom/otherStruct.go index d384b42..c5507f9 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -20,11 +20,13 @@ func CreateAVCDecoderConfRecord( PictureParamSet []byte, ) (self AVCDecoderConfRecord, err error) { if len(SeqenceParamSet) < 4 { - err = fmt.Errorf("invalid SeqenceParamSet") + err = fmt.Errorf("invalid SeqenceParamSet data") return } self.AVCProfileIndication = int(SeqenceParamSet[1]) self.AVCLevelIndication = int(SeqenceParamSet[3]) + self.SeqenceParamSet = [][]byte{SeqenceParamSet} + self.PictureParamSet = [][]byte{PictureParamSet} self.LengthSizeMinusOne = 3 return } diff --git a/atom/reader.go b/atom/reader.go index 28bd77d..afaea0a 100644 --- a/atom/reader.go +++ b/atom/reader.go @@ -45,7 +45,15 @@ func ReadFixed(r io.Reader, n int) (res Fixed, err error) { if ui, err = ReadUInt(r, n); err != nil { return } - res = Fixed(ui) + + if n == 2 { + res = Fixed(ui<<8) + } else if n == 4 { + res = Fixed(ui) + } else { + panic("only fixed32 and fixed16 is supported") + } + return } diff --git a/atom/struct.go b/atom/struct.go index 6606be2..848298d 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -73,10 +73,10 @@ type MovieHeader struct { Flags int CreateTime TimeStamp ModifyTime TimeStamp - TimeScale TimeStamp - Duration TimeStamp - PreferredRate int - PreferredVolume int + TimeScale int + Duration int + PreferredRate Fixed + PreferredVolume Fixed Matrix [9]int PreviewTime TimeStamp PreviewDuration TimeStamp @@ -102,16 +102,16 @@ func ReadMovieHeader(r *io.LimitedReader) (res *MovieHeader, err error) { if self.ModifyTime, err = ReadTimeStamp(r, 4); err != nil { return } - if self.TimeScale, err = ReadTimeStamp(r, 4); err != nil { + if self.TimeScale, err = ReadInt(r, 4); err != nil { return } - if self.Duration, err = ReadTimeStamp(r, 4); err != nil { + if self.Duration, err = ReadInt(r, 4); err != nil { return } - if self.PreferredRate, err = ReadInt(r, 4); err != nil { + if self.PreferredRate, err = ReadFixed(r, 4); err != nil { return } - if self.PreferredVolume, err = ReadInt(r, 2); err != nil { + if self.PreferredVolume, err = ReadFixed(r, 2); err != nil { return } if _, err = ReadDummy(r, 10); err != nil { @@ -165,16 +165,16 @@ func WriteMovieHeader(w io.WriteSeeker, self *MovieHeader) (err error) { if err = WriteTimeStamp(w, self.ModifyTime, 4); err != nil { return } - if err = WriteTimeStamp(w, self.TimeScale, 4); err != nil { + if err = WriteInt(w, self.TimeScale, 4); err != nil { return } - if err = WriteTimeStamp(w, self.Duration, 4); err != nil { + if err = WriteInt(w, self.Duration, 4); err != nil { return } - if err = WriteInt(w, self.PreferredRate, 4); err != nil { + if err = WriteFixed(w, self.PreferredRate, 4); err != nil { return } - if err = WriteInt(w, self.PreferredVolume, 2); err != nil { + if err = WriteFixed(w, self.PreferredVolume, 2); err != nil { return } if err = WriteDummy(w, 10); err != nil { @@ -277,10 +277,10 @@ type TrackHeader struct { CreateTime TimeStamp ModifyTime TimeStamp TrackId int - Duration TimeStamp + Duration int Layer int AlternateGroup int - Volume int + Volume Fixed Matrix [9]int TrackWidth Fixed TrackHeight Fixed @@ -307,7 +307,7 @@ func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { if _, err = ReadDummy(r, 4); err != nil { return } - if self.Duration, err = ReadTimeStamp(r, 4); err != nil { + if self.Duration, err = ReadInt(r, 4); err != nil { return } if _, err = ReadDummy(r, 8); err != nil { @@ -319,7 +319,7 @@ func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) { if self.AlternateGroup, err = ReadInt(r, 2); err != nil { return } - if self.Volume, err = ReadInt(r, 2); err != nil { + if self.Volume, err = ReadFixed(r, 2); err != nil { return } if _, err = ReadDummy(r, 2); err != nil { @@ -364,7 +364,7 @@ func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { if err = WriteDummy(w, 4); err != nil { return } - if err = WriteTimeStamp(w, self.Duration, 4); err != nil { + if err = WriteInt(w, self.Duration, 4); err != nil { return } if err = WriteDummy(w, 8); err != nil { @@ -376,7 +376,7 @@ func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { if err = WriteInt(w, self.AlternateGroup, 2); err != nil { return } - if err = WriteInt(w, self.Volume, 2); err != nil { + if err = WriteFixed(w, self.Volume, 2); err != nil { return } if err = WriteDummy(w, 2); err != nil { @@ -530,8 +530,8 @@ func WriteMedia(w io.WriteSeeker, self *Media) (err error) { type MediaHeader struct { Version int Flags int - CreateTime int - ModifyTime int + CreateTime TimeStamp + ModifyTime TimeStamp TimeScale int Duration int Language int @@ -547,10 +547,10 @@ func ReadMediaHeader(r *io.LimitedReader) (res *MediaHeader, err error) { if self.Flags, err = ReadInt(r, 3); err != nil { return } - if self.CreateTime, err = ReadInt(r, 4); err != nil { + if self.CreateTime, err = ReadTimeStamp(r, 4); err != nil { return } - if self.ModifyTime, err = ReadInt(r, 4); err != nil { + if self.ModifyTime, err = ReadTimeStamp(r, 4); err != nil { return } if self.TimeScale, err = ReadInt(r, 4); err != nil { @@ -581,10 +581,10 @@ func WriteMediaHeader(w io.WriteSeeker, self *MediaHeader) (err error) { if err = WriteInt(w, self.Flags, 3); err != nil { return } - if err = WriteInt(w, self.CreateTime, 4); err != nil { + if err = WriteTimeStamp(w, self.CreateTime, 4); err != nil { return } - if err = WriteInt(w, self.ModifyTime, 4); err != nil { + if err = WriteTimeStamp(w, self.ModifyTime, 4); err != nil { return } if err = WriteInt(w, self.TimeScale, 4); err != nil { diff --git a/atom/writer.go b/atom/writer.go index bdcba26..6ddbc96 100644 --- a/atom/writer.go +++ b/atom/writer.go @@ -34,7 +34,17 @@ func WriteInt(w io.Writer, val int, n int) (err error) { } func WriteFixed(w io.Writer, val Fixed, n int) (err error) { - return WriteUInt(w, uint(val), n) + var uval uint + + if n == 2 { + uval = uint(val)>>8 + } else if n == 4 { + uval = uint(val) + } else { + panic("only fixed32 and fixed16 is supported") + } + + return WriteUInt(w, uval, n) } func WriteTimeStamp(w io.Writer, ts TimeStamp, n int) (err error) { diff --git a/avcc.go b/avcc.go deleted file mode 100644 index b7d1592..0000000 --- a/avcc.go +++ /dev/null @@ -1,17 +0,0 @@ - -package mp4 - -type Avcc struct { - Config []byte - W, H int - Fps int -} - -// [dur][dur][dur][dur] - -// Duration() dur -// FindKeyFrame(at) at, dur -// Read(at) at, dur, buf -// Write(at, buf, dur) at -// RemoveAll() - diff --git a/example/read.go b/example/read.go index 964a27b..3848402 100644 --- a/example/read.go +++ b/example/read.go @@ -13,14 +13,14 @@ func main() { flag.Parse() if *testconv { - if _, err := mp4.TestConvert(flag.Arg(0)); err != nil { + if err := mp4.TestConvert(flag.Arg(0)); err != nil { log.Println(err) return } } if *testrewrite { - if _, err := mp4.TestRewrite(flag.Arg(0)); err != nil { + if err := mp4.TestRewrite(flag.Arg(0)); err != nil { log.Println(err) return } diff --git a/mp4.go b/mp4.go deleted file mode 100644 index 723f798..0000000 --- a/mp4.go +++ /dev/null @@ -1,494 +0,0 @@ - -package mp4 - -import ( - "./atom" - "os" - "io" - "log" - "encoding/hex" -) - -type File struct { -} - -func (self *File) AddAvcc(avcc *Avcc) { -} - -func (self *File) AddMp4a(mp4a *Mp4a) { -} - -func (self *File) GetAvcc() (avcc []*Avcc) { - return -} - -func (self *File) GetMp4a() (mp4a []*Mp4a) { - return -} - -func (self *File) Sync() { -} - -func (self *File) Close() { -} - -func analyzeSamples(sample *atom.SampleTable) { - log.Println("sample:") - - log.Println("HasCompositionOffset", sample.CompositionOffset != nil) - log.Println("SampleCount", len(sample.SampleSize.Entries)) - log.Println("ChunkCount", len(sample.ChunkOffset.Entries)) - - log.Println("SampleToChunkCount", len(sample.SampleToChunk.Entries)) - log.Println("SampleToChunk[0]", sample.SampleToChunk.Entries[0]) - log.Println("SampleToChunk[1]", sample.SampleToChunk.Entries[1]) - - log.Println("TimeToSampleCount", len(sample.TimeToSample.Entries)) - log.Println("TimeToSample[0]", sample.TimeToSample.Entries[0]) - - if sample.SyncSample != nil { - log.Println("SyncSampleCount", len(sample.SyncSample.Entries)) - for i, val := range sample.SyncSample.Entries { - if i < 5 { - log.Println("SyncSample", i, val) - } - } - log.Println("...") - } else { - log.Println("NoSyncSample") - } -} - -func changeMoov(moov *atom.Movie) { - header := moov.Header - - header.CreateTime = atom.TimeStamp(0) - header.ModifyTime = atom.TimeStamp(0) - - if true { - //log.Println("moov: ", header.CreateTime, header.TimeScale, header.Duration) - log.Println("moov: ", header.PreferredRate, header.PreferredVolume) - //log.Println("moov: ", header.PreviewTime, header.PreviewDuration) - //log.Println("moov: ", header.PosterTime) - //log.Println("moov: ", header.SelectionTime, header.SelectionDuration) - //log.Println("moov: ", header.CurrentTime) - //log.Println("moov: ", header.NextTrackId) - //log.Println("moov: ", header.Matrix) - } - - for i, track := range moov.Tracks { - if true { - log.Println("track", i, ":", track.Header.TrackId) - log.Println("track", i, ":", track.Header.Duration) - //log.Println("track", i, ":", track.Header.Layer, track.Header.AlternateGroup) - log.Println("track", i, ":", track.Header.Volume) - log.Println("track", i, ":", track.Header.TrackWidth, track.Header.TrackHeight) - log.Println("track", i, ":", track.Header.Matrix) - } - - media := track.Media - if true { - log.Println("mediaHeader", media.Header) - log.Println("media.hdlr", media.Handler) - } - - if minf := media.Info; minf != nil { - - if true { - log.Println("minf.video", minf.Video) - } - - if sample := minf.Sample; sample != nil { - analyzeSamples(sample) - - if desc := sample.SampleDesc; desc != nil { - - if avc1Desc := desc.Avc1Desc; avc1Desc != nil { - if conf := avc1Desc.Conf; conf != nil { - if true { - //log.Println("avc1", hex.Dump(conf.Data)) - log.Println("avc1desc", conf) - //avcconf, _ := atom.ReadAVCDecoderConfRecord(bytes.NewReader(conf.Data)) - //log.Println("avcconf", avcconf) - } - } - } - - if mp4a := desc.Mp4aDesc; mp4a != nil { - if conf := mp4a.Conf; conf != nil { - if false { - log.Println("mp4a", hex.Dump(conf.Data)) - } - } - } - - } - } - } - - } - -} - -type Sample struct { - Time int - Data []byte - Sync bool -} - -func readSamples(vsample *atom.SampleTable, mdat io.ReadSeeker, out chan<- Sample) { - sampleToChunkIdx := 0 - chunkIdx := 0 - nextChunkIdx := 0 - samplesPerChunk := 0 - - updateSamplesPerChunk := func() { - chunkIdx = vsample.SampleToChunk.Entries[sampleToChunkIdx].FirstChunk-1 - samplesPerChunk = vsample.SampleToChunk.Entries[sampleToChunkIdx].SamplesPerChunk - sampleToChunkIdx++ - if sampleToChunkIdx < len(vsample.SampleToChunk.Entries) { - nextChunkIdx = vsample.SampleToChunk.Entries[sampleToChunkIdx].FirstChunk-1 - } else { - nextChunkIdx = -1 - } - } - updateSamplesPerChunk() - - timeToSampleIdx := 0 - timeToSampleCount := 0 - sampleTime := 0 - - sampleIdx := 0 - sampleNr := len(vsample.SampleSize.Entries) - - syncSampleIdx := 0 - syncSample := vsample.SyncSample.Entries; - - for sampleIdx < sampleNr { - if chunkIdx == nextChunkIdx { - updateSamplesPerChunk() - } - sampleOffset := vsample.ChunkOffset.Entries[chunkIdx] - for i := 0; i < samplesPerChunk; i++ { - sampleSize := vsample.SampleSize.Entries[sampleIdx] - - mdat.Seek(int64(sampleOffset), 0) - data := make([]byte, sampleSize) - mdat.Read(data) - - var sync bool - if syncSampleIdx < len(syncSample) && syncSample[syncSampleIdx]-1 == sampleIdx { - sync = true - syncSampleIdx++ - } - - out <- Sample{ - Time: sampleTime, - Data: data, - Sync: sync, - } - - sampleOffset += sampleSize - sampleIdx++ - - sampleTime += vsample.TimeToSample.Entries[timeToSampleIdx].Duration - timeToSampleCount++ - if timeToSampleCount == vsample.TimeToSample.Entries[timeToSampleIdx].Count { - timeToSampleCount = 0 - timeToSampleIdx++ - } - } - chunkIdx++ - } - - close(out) -} - -func rewrite(moov *atom.Movie, mdat io.ReadSeeker, outfile io.WriteSeeker) (err error) { - var vtrack *atom.Track - var vsample *atom.SampleTable - - for _, track := range moov.Tracks { - media := track.Media - if minf := media.Info; minf != nil { - if sample := minf.Sample; sample != nil { - if desc := sample.SampleDesc; desc != nil { - if avc1Desc := desc.Avc1Desc; avc1Desc != nil { - if conf := avc1Desc.Conf; conf != nil { - vtrack = track - vsample = sample - } - } - } - } - } - } - - sampleCh := make(chan Sample) - go readSamples(vsample, mdat, sampleCh) - - log.Println("avc1Desc.conf", vsample.SampleDesc.Avc1Desc.Conf) - - newsample := &atom.SampleTable{ - SampleDesc: &atom.SampleDesc{ - Avc1Desc: &atom.Avc1Desc{ - Conf: vsample.SampleDesc.Avc1Desc.Conf, - }, - }, - TimeToSample: &atom.TimeToSample{}, - SampleToChunk: &atom.SampleToChunk{ - Entries: []atom.SampleToChunkEntry{ - { - FirstChunk: 1, - SampleDescId: 1, - }, - }, - }, - SampleSize: &atom.SampleSize{}, - ChunkOffset: &atom.ChunkOffset{ - Entries: []int{8}, - }, - SyncSample: &atom.SyncSample{}, - } - sampleToChunk := &newsample.SampleToChunk.Entries[0] - - var timeToSample *atom.TimeToSampleEntry - - mdatWriter, _ := atom.WriteAtomHeader(outfile, "mdat") - - for sampleIdx := 1; ; sampleIdx++ { - if sample, ok := <-sampleCh; ok { - if sampleIdx < 10 { - log.Println( - sampleIdx, - "sampleTime", float32(sample.Time)/float32(vtrack.Media.Header.TimeScale)/60.0, - "len", len(sample.Data), - //"timeToSampleIdx", timeToSampleIdx, - ) - } - - sampleSize := len(sample.Data) - sampleDuration := 1000 - mdatWriter.Write(sample.Data) - - if sample.Sync { - newsample.SyncSample.Entries = append(newsample.SyncSample.Entries, sampleIdx) - } - - if timeToSample != nil && sampleDuration != timeToSample.Duration { - newsample.TimeToSample.Entries = append(newsample.TimeToSample.Entries, *timeToSample) - timeToSample = nil - } - if timeToSample == nil { - timeToSample = &atom.TimeToSampleEntry{ - Duration: sampleDuration, - } - } - timeToSample.Count++ - - sampleToChunk.SamplesPerChunk++ - - newsample.SampleSize.Entries = append(newsample.SampleSize.Entries, sampleSize) - } else { - break - } - } - - if timeToSample != nil { - newsample.TimeToSample.Entries = append(newsample.TimeToSample.Entries, *timeToSample) - } - - mdatWriter.Close() - - newmoov := &atom.Movie{} - newmoov.Header = &atom.MovieHeader{ - TimeScale: moov.Header.TimeScale, - Duration: moov.Header.Duration, - PreferredRate: moov.Header.PreferredRate, - PreferredVolume: moov.Header.PreferredVolume, - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - } - - newtrack := &atom.Track{ - Header: &atom.TrackHeader{ - Flags: 0x0001, // enabled - Duration: vtrack.Header.Duration, - Volume: vtrack.Header.Volume, - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - //TrackWidth: vtrack.Header.TrackWidth, - //TrackHeight: vtrack.Header.TrackHeight, - TrackId: 1, - }, - - Media: &atom.Media{ - Header: &atom.MediaHeader{ - TimeScale: vtrack.Media.Header.TimeScale, - Duration: vtrack.Media.Header.Duration, - }, - Info: &atom.MediaInfo{ - Video: &atom.VideoMediaInfo{ - Flags: 0x000001, - }, - Sample: newsample, - }, - Handler: &atom.HandlerRefer{ - SubType: "vide", - Name: "Video Media Handler", - }, - }, - } - newmoov.Tracks = append(newmoov.Tracks, newtrack) - - atom.WriteMovie(outfile, newmoov) - - return -} - -func TestRewrite(filename string) (file *File, err error) { - var infile *os.File - if infile, err = os.Open(filename); err != nil { - return - } - - var finfo os.FileInfo - if finfo, err = infile.Stat(); err != nil { - return - } - lr := &io.LimitedReader{R: infile, N: finfo.Size()} - - var moov *atom.Movie - mdatOffset := int64(-1) - - for lr.N > 0 { - var ar *io.LimitedReader - - var cc4 string - if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { - return - } - log.Println("cc4", cc4) - - if cc4 == "moov" { - if moov, err = atom.ReadMovie(ar); err != nil { - return - } - } else if cc4 == "mdat" { - mdatOffset, _ = infile.Seek(0, 1) - break - } - - if _, err = atom.ReadDummy(lr, int(ar.N)); err != nil { - return - } - } - - if mdatOffset == -1 { - log.Println("mdat not found") - return - } - - outfileName := filename+".out.mp4" - var outfile *os.File - if outfile, err = os.Create(outfileName); err != nil { - return - } - - if err = rewrite(moov, infile, outfile); err != nil { - return - } - - if err = outfile.Close(); err != nil { - return - } - log.Println("output file", outfileName, "saved") - - return -} - -func TestConvert(filename string) (file *File, err error) { - var osfile *os.File - if osfile, err = os.Open(filename); err != nil { - return - } - - var finfo os.FileInfo - if finfo, err = osfile.Stat(); err != nil { - return - } - log.Println("filesize", finfo.Size()) - - lr := &io.LimitedReader{R: osfile, N: finfo.Size()} - - var outfile *os.File - if outfile, err = os.Create(filename+".out.mp4"); err != nil { - return - } - - for lr.N > 0 { - var ar *io.LimitedReader - - var cc4 string - if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { - return - } - - if cc4 == "moov" { - - curPos, _ := outfile.Seek(0, 1) - origSize := ar.N+8 - var moov *atom.Movie - if moov, err = atom.ReadMovie(ar); err != nil { - return - } - changeMoov(moov) - if err = atom.WriteMovie(outfile, moov); err != nil { - return - } - curPosAfterRead, _ := outfile.Seek(0, 1) - bytesWritten := curPosAfterRead - curPos - - log.Println("regen moov", "tracks nr", len(moov.Tracks), - "origSize", origSize, "bytesWritten", bytesWritten, - ) - - padSize := origSize - bytesWritten - 8 - aw, _ := atom.WriteAtomHeader(outfile, "free") - atom.WriteDummy(outfile, int(padSize)) - aw.Close() - - } else { - - var outcc4 string - if cc4 != "mdat" { - outcc4 = "free" - } else { - outcc4 = "mdat" - } - var aw *atom.Writer - if aw, err = atom.WriteAtomHeader(outfile, outcc4); err != nil { - return - } - log.Println("copy", cc4) - if _, err = io.CopyN(aw, ar, ar.N); err != nil { - return - } - if err = aw.Close(); err != nil { - return - } - } - - //log.Println("atom", cc4, "left", lr.N) - //atom.ReadDummy(ar, int(ar.N)) - } - - if err = outfile.Close(); err != nil { - return - } - - return -} - -func Create(filename string) (file *File, err error) { - return -} - diff --git a/mp4a.go b/mp4a.go deleted file mode 100644 index 614a430..0000000 --- a/mp4a.go +++ /dev/null @@ -1,15 +0,0 @@ - -package mp4 - -type Mp4a struct { - Config []byte - SampleRate int - Channels int -} - -// [dur][dur][dur][dur] - -// Read() -// Write(i, buf, dur) -// RemoveAll() - diff --git a/writer.go b/writer.go new file mode 100644 index 0000000..6d60d2c --- /dev/null +++ b/writer.go @@ -0,0 +1,156 @@ + +package mp4 + +import ( + "./atom" + "io" +) + +type SimpleH264Writer struct { + W io.WriteSeeker + + TimeScale int + SPS []byte + PPS []byte + + duration int + + sample *atom.SampleTable + sampleToChunk *atom.SampleToChunkEntry + sampleIdx int + timeToSample *atom.TimeToSampleEntry + + mdatWriter *atom.Writer +} + +func (self *SimpleH264Writer) prepare() (err error) { + if self.mdatWriter, err = atom.WriteAtomHeader(self.W, "mdat"); err != nil { + return + } + + self.sampleIdx = 1 + + self.sample = &atom.SampleTable{ + SampleDesc: &atom.SampleDesc{ + Avc1Desc: &atom.Avc1Desc{ + Conf: &atom.Avc1Conf{}, + }, + }, + TimeToSample: &atom.TimeToSample{}, + SampleToChunk: &atom.SampleToChunk{ + Entries: []atom.SampleToChunkEntry{ + { + FirstChunk: 1, + SampleDescId: 1, + }, + }, + }, + SampleSize: &atom.SampleSize{}, + ChunkOffset: &atom.ChunkOffset{ + Entries: []int{8}, + }, + SyncSample: &atom.SyncSample{}, + } + self.sampleToChunk = &self.sample.SampleToChunk.Entries[0] + + return +} + +func (self *SimpleH264Writer) WriteSample(sync bool, duration int, data []byte) (err error) { + if self.mdatWriter == nil { + if err = self.prepare(); err != nil { + return + } + } + + if _, err = self.mdatWriter.Write(data); err != nil { + return + } + + if sync { + self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, self.sampleIdx) + } + + if self.timeToSample != nil && duration != self.timeToSample.Duration { + self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, *self.timeToSample) + self.timeToSample = nil + } + if self.timeToSample == nil { + self.timeToSample = &atom.TimeToSampleEntry{ + Duration: duration, + } + } + + self.duration += duration + self.sampleIdx++ + self.timeToSample.Count++ + self.sampleToChunk.SamplesPerChunk++ + self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, len(data)) + + return +} + +func (self *SimpleH264Writer) Finish() (err error) { + self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( + self.SPS, + self.PPS, + ) + if err != nil { + return + } + + if self.timeToSample != nil { + self.sample.TimeToSample.Entries = append( + self.sample.TimeToSample.Entries, + *self.timeToSample, + ) + } + + if err = self.mdatWriter.Close(); err != nil { + return + } + + moov := &atom.Movie{} + moov.Header = &atom.MovieHeader{ + TimeScale: self.TimeScale, + Duration: self.duration, + PreferredRate: atom.IntToFixed(1), + PreferredVolume: atom.IntToFixed(1), + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + } + + track := &atom.Track{ + Header: &atom.TrackHeader{ + Flags: 0x0001, // enabled + Duration: self.duration, + Volume: atom.IntToFixed(1), + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + TrackId: 1, + }, + + Media: &atom.Media{ + Header: &atom.MediaHeader{ + TimeScale: self.TimeScale, + Duration: self.duration, + }, + Info: &atom.MediaInfo{ + Video: &atom.VideoMediaInfo{ + Flags: 0x000001, + }, + Sample: self.sample, + }, + Handler: &atom.HandlerRefer{ + SubType: "vide", + Name: "Video Media Handler", + }, + }, + } + moov.Tracks = append(moov.Tracks, track) + + if err = atom.WriteMovie(self.W, moov); err != nil { + return + } + + return +} + From cdeeeaa557fc58b78e38b9e6f15bfca650066afe Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 25 Nov 2015 10:11:15 +0800 Subject: [PATCH 17/93] change non-local import path --- writer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/writer.go b/writer.go index 6d60d2c..1f6eff8 100644 --- a/writer.go +++ b/writer.go @@ -2,7 +2,7 @@ package mp4 import ( - "./atom" + "github.com/nareix/mp4/atom" "io" ) From 90b650ed179fbcacb4af4518e602ee775a1bbdcc Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 25 Nov 2015 12:50:28 +0800 Subject: [PATCH 18/93] support WriteNALU --- atom/otherStruct.go | 12 ++++++++++++ writer.go | 21 +++++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/atom/otherStruct.go b/atom/otherStruct.go index c5507f9..53fe639 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -15,6 +15,18 @@ type AVCDecoderConfRecord struct { PictureParamSet [][]byte } +func WriteSampleByNALU(w io.Writer, nalu []byte) (size int, err error) { + if err = WriteInt(w, len(nalu), 4); err != nil { + return + } + size += 4 + if _, err = w.Write(nalu); err != nil { + return + } + size += len(nalu) + return +} + func CreateAVCDecoderConfRecord( SeqenceParamSet []byte, PictureParamSet []byte, diff --git a/writer.go b/writer.go index 1f6eff8..0c34481 100644 --- a/writer.go +++ b/writer.go @@ -57,13 +57,30 @@ func (self *SimpleH264Writer) prepare() (err error) { } func (self *SimpleH264Writer) WriteSample(sync bool, duration int, data []byte) (err error) { + return self.writeSample(func (w io.Writer, data []byte) (int, error) { + if _, err = self.mdatWriter.Write(data); err != nil { + return 0, err + } + return len(data), nil + }, sync, duration, data) +} + +func (self *SimpleH264Writer) WriteNALU(sync bool, duration int, data []byte) (err error) { + return self.writeSample(atom.WriteSampleByNALU, sync, duration, data) +} + +func (self *SimpleH264Writer) writeSample( + writeFunc func(io.Writer, []byte) (int,error), + sync bool, duration int, data []byte, +) (err error) { if self.mdatWriter == nil { if err = self.prepare(); err != nil { return } } - if _, err = self.mdatWriter.Write(data); err != nil { + var sampleSize int + if sampleSize, err = writeFunc(self.mdatWriter, data); err != nil { return } @@ -85,7 +102,7 @@ func (self *SimpleH264Writer) WriteSample(sync bool, duration int, data []byte) self.sampleIdx++ self.timeToSample.Count++ self.sampleToChunk.SamplesPerChunk++ - self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, len(data)) + self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, sampleSize) return } From 0a8a72ae7d3788e44f93ca2a4ec352fd24d1a3d9 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 26 Nov 2015 16:21:26 +0800 Subject: [PATCH 19/93] add isNALU param to writeSample --- writer.go | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/writer.go b/writer.go index 0c34481..28f7355 100644 --- a/writer.go +++ b/writer.go @@ -57,22 +57,29 @@ func (self *SimpleH264Writer) prepare() (err error) { } func (self *SimpleH264Writer) WriteSample(sync bool, duration int, data []byte) (err error) { - return self.writeSample(func (w io.Writer, data []byte) (int, error) { - if _, err = self.mdatWriter.Write(data); err != nil { - return 0, err - } - return len(data), nil - }, sync, duration, data) + return self.writeSample(false, sync, duration, data) } func (self *SimpleH264Writer) WriteNALU(sync bool, duration int, data []byte) (err error) { - return self.writeSample(atom.WriteSampleByNALU, sync, duration, data) + return self.writeSample(true, sync, duration, data) } -func (self *SimpleH264Writer) writeSample( - writeFunc func(io.Writer, []byte) (int,error), - sync bool, duration int, data []byte, -) (err error) { +func splitNALUByStartCode(data []byte) (out [][]byte) { + last := 0 + for i := 0; i < len(data)-3; { + if data[i] == 0 && data[i+1] == 0 && data[i+2] == 1 { + out = append(out, data[last:i]) + i += 3 + last = i + } else { + i++ + } + } + out = append(out, data[last:]) + return +} + +func (self *SimpleH264Writer) writeSample(isNALU, sync bool, duration int, data []byte) (err error) { if self.mdatWriter == nil { if err = self.prepare(); err != nil { return @@ -80,8 +87,16 @@ func (self *SimpleH264Writer) writeSample( } var sampleSize int - if sampleSize, err = writeFunc(self.mdatWriter, data); err != nil { - return + + if isNALU { + if sampleSize, err = atom.WriteSampleByNALU(self.mdatWriter, data); err != nil { + return + } + } else { + sampleSize = len(data) + if _, err = self.mdatWriter.Write(data); err != nil { + return + } } if sync { From 60176b7f5029b8a83fec32344bb84e3c4f8ad1ef Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 26 Nov 2015 18:42:43 +0800 Subject: [PATCH 20/93] add iods --- atom/genStruct.js | 8 ++++++++ atom/struct.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/atom/genStruct.js b/atom/genStruct.js index c62ac57..9c9e224 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -10,11 +10,19 @@ var atoms = { fields: [ ['$atoms', [ ['header', '*movieHeader'], + ['iods', '*iods'], ['tracks', '[]*track'], ]], ], }, + iods: { + cc4: 'iods', + fields: [ + ['data', '[]byte'], + ], + }, + movieHeader: { cc4: 'mvhd', fields: [ diff --git a/atom/struct.go b/atom/struct.go index 848298d..b8def2f 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -7,6 +7,7 @@ import ( type Movie struct { Header *MovieHeader + Iods *Iods Tracks []*Track } @@ -26,6 +27,12 @@ func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { return } } + case "iods": + { + if self.Iods, err = ReadIods(ar); err != nil { + return + } + } case "trak": { var item *Track @@ -55,6 +62,11 @@ func WriteMovie(w io.WriteSeeker, self *Movie) (err error) { return } } + if self.Iods != nil { + if err = WriteIods(w, self.Iods); err != nil { + return + } + } if self.Tracks != nil { for _, elem := range self.Tracks { if err = WriteTrack(w, elem); err != nil { @@ -68,6 +80,35 @@ func WriteMovie(w io.WriteSeeker, self *Movie) (err error) { return } +type Iods struct { + Data []byte +} + +func ReadIods(r *io.LimitedReader) (res *Iods, err error) { + + self := &Iods{} + if self.Data, err = ReadBytes(r, int(r.N)); err != nil { + return + } + res = self + return +} +func WriteIods(w io.WriteSeeker, self *Iods) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "iods"); err != nil { + return + } + w = aw + if err = WriteBytes(w, self.Data, len(self.Data)); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} + type MovieHeader struct { Version int Flags int From 10cf2743c701fec55bcf93e660ed03725406a445 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 27 Nov 2015 15:06:44 +0800 Subject: [PATCH 21/93] add dumper --- atom/dumper.go | 69 +++++ atom/genStruct.js | 167 ++++++++--- atom/genStruct.sh | 4 + atom/otherStruct.go | 3 + atom/struct.go | 672 ++++++++++++++++++++++++++++++++++++++++++++ example/read.go | 6 +- test.go | 161 +++++++++++ writer.go | 7 + 8 files changed, 1052 insertions(+), 37 deletions(-) create mode 100644 atom/dumper.go create mode 100755 atom/genStruct.sh create mode 100644 test.go diff --git a/atom/dumper.go b/atom/dumper.go new file mode 100644 index 0000000..2403036 --- /dev/null +++ b/atom/dumper.go @@ -0,0 +1,69 @@ + +package atom + +import ( + "fmt" + "strings" + "encoding/hex" +) + +type Walker interface { + Start() + Log(string) + Name(string) + Int(int) + Fixed(Fixed) + String(string) + Bytes([]byte) + TimeStamp(TimeStamp) + End() +} + +type Dumper struct { + depth int +} + +func (self *Dumper) Start() { + self.depth++ +} + +func (self *Dumper) End() { + self.depth-- +} + +func (self Dumper) tab() string { + return strings.Repeat(" ", self.depth*2) +} + +func (self Dumper) Name(name string) { + fmt.Print(self.tab(), name, ": ") +} + +func (self Dumper) Log(msg string) { + fmt.Println(self.tab()+msg) +} + +func (self Dumper) logVal(msg string) { + fmt.Println(msg) +} + +func (self Dumper) Int(val int) { + self.logVal(fmt.Sprintf("%d", val)) +} + +func (self Dumper) Fixed(val Fixed) { + self.logVal(fmt.Sprintf("%d", FixedToInt(val))) +} + +func (self Dumper) String(val string) { + self.logVal(val) +} + +func (self Dumper) Bytes(val []byte) { + self.logVal(hex.EncodeToString(val)) +} + +func (self Dumper) TimeStamp(val TimeStamp) { + self.logVal(fmt.Sprintf("%d", int(val))) +} + diff --git a/atom/genStruct.js b/atom/genStruct.js index 9c9e224..b7efad8 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -119,11 +119,42 @@ var atoms = { ['$atoms', [ ['sound', '*soundMediaInfo'], ['video', '*videoMediaInfo'], + ['data', '*dataInfo'], ['sample', '*sampleTable'], ]], ], }, + dataInfo: { + cc4: 'dinf', + fields: [ + ['$atoms', [ + ['refer', '*dataRefer'], + ]], + ], + }, + + dataRefer: { + cc4: 'dref', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + + ['$atomsCount', 'int32'], + ['$atoms', [ + ['url', '*dataReferUrl'], + ]], + ], + }, + + dataReferUrl: { + cc4: 'url ', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ], + }, + soundMediaInfo: { cc4: 'smhd', fields: [ @@ -487,6 +518,57 @@ var DeclWriteFunc = (opts) => { ); }; +var DeclDumpFunc = (opts) => { + var dumpStruct = (name, type) => { + if (type.ptr) + return If(`${name} != nil`, Call('Walk'+type.Struct, ['w', name])); + return Call('Walk'+type.Struct, ['w', name]); + }; + + var dumpArr = (name, type, id) => { + return For(`_, item := range(${name})`, [ + dumpCommonType('item', type, id), + ]); + }; + + var dumpCommonType = (name, type, id) => { + if (type.struct) + return dumpStruct(name, type); + return [ + Call('w.Name', [`"${id}"`]), + Call('w.'+type.fn, [name]), + ]; + }; + + var dumpField = (name, type, noarr) => { + if (name == '_') + return; + if (name == '$atomsCount') + return; + if (name == '$atoms') { + return type.list.map(field => dumpField(field.name, field.type)); + } + if (!noarr && type.arr && type.fn != 'Bytes') + return dumpArr('self.'+name, type, name); + return dumpCommonType('self.'+name, type, name); + }; + + var dumpFields = fields => + [ + Call('w.Log', [`"[${opts.type}]"`]), + Call('w.Start', []), + ] + .concat(fields.map(field => dumpField(field.name, field.type))) + .concat([Call('w.End', [])]); + + return Func( + 'Walk'+opts.type, + [['w', 'Walker'], ['self', (opts.cc4?'*':'')+opts.type]], + [], + dumpFields(opts.fields) + ) +}; + var D = (cls, ...fields) => { global[cls] = (...args) => { var obj = {cls: cls}; @@ -496,6 +578,8 @@ var D = (cls, ...fields) => { }; D('Func', 'name', 'args', 'rets', 'body'); +D('If', 'cond', 'action', 'else'); +D('Call', 'fn', 'args'); D('CallCheckAssign', 'fn', 'args', 'rets', 'action'); D('DeclVar', 'name', 'type'); D('For', 'cond', 'body'); @@ -516,42 +600,51 @@ var dumpFn = f => { }`; }; -var dumpStmts = stmts => { - var dumpStmt = stmt => { - if (typeof(stmt) == 'string') { - return stmt; - } else if (stmt instanceof Array) { - return dumpStmts(stmt); - } if (stmt.cls == 'CallCheckAssign') { - return `if ${stmt.rets.concat(['err']).join(',')} = ${stmt.fn}(${stmt.args.join(',')}); err != nil { - ${stmt.action ? stmt.action : 'return'} - }`; - } else if (stmt.cls == 'DeclVar') { - return `var ${stmt.name} ${stmt.type}`; - } else if (stmt.cls == 'For') { - return `for ${dumpStmt(stmt.cond)} { - ${dumpStmts(stmt.body)} - }`; - } else if (stmt.cls == 'RangeN') { - return `${stmt.i} := 0; ${stmt.i} < ${stmt.n}; ${stmt.i}++`; - } else if (stmt.cls == 'DeclStruct') { - return `type ${stmt.name} struct { - ${stmt.body.map(line => line.join(' ')).join('\n')} - }`; - } else if (stmt.cls == 'Func') { - return dumpFn(stmt); - } else if (stmt.cls == 'StrStmt') { - return stmt.content; - } else if (stmt.cls == 'Switch') { - var dumpCase = c => `case ${c[0]}: { ${dumpStmts(c[1])} }`; - var dumpDefault = c => `default: { ${dumpStmts(c)} }`; - return `switch ${stmt.cond} { - ${stmt.cases.map(dumpCase).join('\n')} - ${stmt.default && dumpDefault(stmt.default) || ''} +var dumpStmts = stmt => { + if (typeof(stmt) == 'string') { + return stmt; + } else if (stmt instanceof Array) { + return stmt.nonull().map(dumpStmts).join('\n'); + } else if (stmt.cls == 'If') { + var s = `if ${stmt.cond} { + ${dumpStmts(stmt.action)} + }`; + if (stmt.else) { + s += ` else { + ${dumpStmts(stmt.else)} }`; } - }; - return stmts.nonull().map(dumpStmt).join('\n') + return s; + } else if (stmt.cls == 'Call') { + return `${stmt.fn}(${stmt.args.join(',')})`; + } else if (stmt.cls == 'CallCheckAssign') { + return `if ${stmt.rets.concat(['err']).join(',')} = ${stmt.fn}(${stmt.args.join(',')}); err != nil { + ${stmt.action ? stmt.action : 'return'} + }`; + } else if (stmt.cls == 'DeclVar') { + return `var ${stmt.name} ${stmt.type}`; + } else if (stmt.cls == 'For') { + return `for ${dumpStmts(stmt.cond)} { + ${dumpStmts(stmt.body)} + }`; + } else if (stmt.cls == 'RangeN') { + return `${stmt.i} := 0; ${stmt.i} < ${stmt.n}; ${stmt.i}++`; + } else if (stmt.cls == 'DeclStruct') { + return `type ${stmt.name} struct { + ${stmt.body.map(line => line.join(' ')).join('\n')} + }`; + } else if (stmt.cls == 'Func') { + return dumpFn(stmt); + } else if (stmt.cls == 'StrStmt') { + return stmt.content; + } else if (stmt.cls == 'Switch') { + var dumpCase = c => `case ${c[0]}: { ${dumpStmts(c[1])} }`; + var dumpDefault = c => `default: { ${dumpStmts(c)} }`; + return `switch ${stmt.cond} { + ${stmt.cases.map(dumpCase).join('\n')} + ${stmt.default && dumpDefault(stmt.default) || ''} + }`; + } }; var parseType = s => { @@ -667,6 +760,12 @@ var allStmts = () => { fields: fields, cc4: atom.cc4, }), + + DeclDumpFunc({ + type: name, + fields: fields, + cc4: atom.cc4, + }), ]); } diff --git a/atom/genStruct.sh b/atom/genStruct.sh new file mode 100755 index 0000000..eebb079 --- /dev/null +++ b/atom/genStruct.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +node --harmony_rest_parameters genStruct.js > struct.go && gofmt -w struct.go && go build . + diff --git a/atom/otherStruct.go b/atom/otherStruct.go index 53fe639..de77e22 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -87,6 +87,9 @@ func WriteAVCDecoderConfRecord(w io.Writer, self AVCDecoderConfRecord) (err erro return } +func WalkAVCDecoderConfRecord(w Walker, self AVCDecoderConfRecord) { +} + func ReadAVCDecoderConfRecord(r *io.LimitedReader) (self AVCDecoderConfRecord, err error) { if _, err = ReadDummy(r, 1); err != nil { return diff --git a/atom/struct.go b/atom/struct.go index b8def2f..85689c6 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -79,6 +79,24 @@ func WriteMovie(w io.WriteSeeker, self *Movie) (err error) { } return } +func WalkMovie(w Walker, self *Movie) { + + w.Log("[Movie]") + w.Start() + if self.Header != nil { + WalkMovieHeader(w, self.Header) + } + if self.Iods != nil { + WalkIods(w, self.Iods) + } + for _, item := range self.Tracks { + if item != nil { + WalkTrack(w, item) + } + } + w.End() + return +} type Iods struct { Data []byte @@ -108,6 +126,15 @@ func WriteIods(w io.WriteSeeker, self *Iods) (err error) { } return } +func WalkIods(w Walker, self *Iods) { + + w.Log("[Iods]") + w.Start() + w.Name("Data") + w.Bytes(self.Data) + w.End() + return +} type MovieHeader struct { Version int @@ -252,6 +279,47 @@ func WriteMovieHeader(w io.WriteSeeker, self *MovieHeader) (err error) { } return } +func WalkMovieHeader(w Walker, self *MovieHeader) { + + w.Log("[MovieHeader]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("CreateTime") + w.TimeStamp(self.CreateTime) + w.Name("ModifyTime") + w.TimeStamp(self.ModifyTime) + w.Name("TimeScale") + w.Int(self.TimeScale) + w.Name("Duration") + w.Int(self.Duration) + w.Name("PreferredRate") + w.Fixed(self.PreferredRate) + w.Name("PreferredVolume") + w.Fixed(self.PreferredVolume) + for _, item := range self.Matrix { + w.Name("Matrix") + w.Int(item) + } + w.Name("PreviewTime") + w.TimeStamp(self.PreviewTime) + w.Name("PreviewDuration") + w.TimeStamp(self.PreviewDuration) + w.Name("PosterTime") + w.TimeStamp(self.PosterTime) + w.Name("SelectionTime") + w.TimeStamp(self.SelectionTime) + w.Name("SelectionDuration") + w.TimeStamp(self.SelectionDuration) + w.Name("CurrentTime") + w.TimeStamp(self.CurrentTime) + w.Name("NextTrackId") + w.Int(self.NextTrackId) + w.End() + return +} type Track struct { Header *TrackHeader @@ -311,6 +379,19 @@ func WriteTrack(w io.WriteSeeker, self *Track) (err error) { } return } +func WalkTrack(w Walker, self *Track) { + + w.Log("[Track]") + w.Start() + if self.Header != nil { + WalkTrackHeader(w, self.Header) + } + if self.Media != nil { + WalkMedia(w, self.Media) + } + w.End() + return +} type TrackHeader struct { Version int @@ -439,6 +520,39 @@ func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { } return } +func WalkTrackHeader(w Walker, self *TrackHeader) { + + w.Log("[TrackHeader]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("CreateTime") + w.TimeStamp(self.CreateTime) + w.Name("ModifyTime") + w.TimeStamp(self.ModifyTime) + w.Name("TrackId") + w.Int(self.TrackId) + w.Name("Duration") + w.Int(self.Duration) + w.Name("Layer") + w.Int(self.Layer) + w.Name("AlternateGroup") + w.Int(self.AlternateGroup) + w.Name("Volume") + w.Fixed(self.Volume) + for _, item := range self.Matrix { + w.Name("Matrix") + w.Int(item) + } + w.Name("TrackWidth") + w.Fixed(self.TrackWidth) + w.Name("TrackHeight") + w.Fixed(self.TrackHeight) + w.End() + return +} type HandlerRefer struct { Version int @@ -496,6 +610,23 @@ func WriteHandlerRefer(w io.WriteSeeker, self *HandlerRefer) (err error) { } return } +func WalkHandlerRefer(w Walker, self *HandlerRefer) { + + w.Log("[HandlerRefer]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("Type") + w.String(self.Type) + w.Name("SubType") + w.String(self.SubType) + w.Name("Name") + w.String(self.Name) + w.End() + return +} type Media struct { Header *MediaHeader @@ -567,6 +698,22 @@ func WriteMedia(w io.WriteSeeker, self *Media) (err error) { } return } +func WalkMedia(w Walker, self *Media) { + + w.Log("[Media]") + w.Start() + if self.Header != nil { + WalkMediaHeader(w, self.Header) + } + if self.Info != nil { + WalkMediaInfo(w, self.Info) + } + if self.Handler != nil { + WalkHandlerRefer(w, self.Handler) + } + w.End() + return +} type MediaHeader struct { Version int @@ -645,10 +792,34 @@ func WriteMediaHeader(w io.WriteSeeker, self *MediaHeader) (err error) { } return } +func WalkMediaHeader(w Walker, self *MediaHeader) { + + w.Log("[MediaHeader]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("CreateTime") + w.TimeStamp(self.CreateTime) + w.Name("ModifyTime") + w.TimeStamp(self.ModifyTime) + w.Name("TimeScale") + w.Int(self.TimeScale) + w.Name("Duration") + w.Int(self.Duration) + w.Name("Language") + w.Int(self.Language) + w.Name("Quality") + w.Int(self.Quality) + w.End() + return +} type MediaInfo struct { Sound *SoundMediaInfo Video *VideoMediaInfo + Data *DataInfo Sample *SampleTable } @@ -674,6 +845,12 @@ func ReadMediaInfo(r *io.LimitedReader) (res *MediaInfo, err error) { return } } + case "dinf": + { + if self.Data, err = ReadDataInfo(ar); err != nil { + return + } + } case "stbl": { if self.Sample, err = ReadSampleTable(ar); err != nil { @@ -706,6 +883,11 @@ func WriteMediaInfo(w io.WriteSeeker, self *MediaInfo) (err error) { return } } + if self.Data != nil { + if err = WriteDataInfo(w, self.Data); err != nil { + return + } + } if self.Sample != nil { if err = WriteSampleTable(w, self.Sample); err != nil { return @@ -716,6 +898,216 @@ func WriteMediaInfo(w io.WriteSeeker, self *MediaInfo) (err error) { } return } +func WalkMediaInfo(w Walker, self *MediaInfo) { + + w.Log("[MediaInfo]") + w.Start() + if self.Sound != nil { + WalkSoundMediaInfo(w, self.Sound) + } + if self.Video != nil { + WalkVideoMediaInfo(w, self.Video) + } + if self.Data != nil { + WalkDataInfo(w, self.Data) + } + if self.Sample != nil { + WalkSampleTable(w, self.Sample) + } + w.End() + return +} + +type DataInfo struct { + Refer *DataRefer +} + +func ReadDataInfo(r *io.LimitedReader) (res *DataInfo, err error) { + + self := &DataInfo{} + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "dref": + { + if self.Refer, err = ReadDataRefer(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + res = self + return +} +func WriteDataInfo(w io.WriteSeeker, self *DataInfo) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "dinf"); err != nil { + return + } + w = aw + if self.Refer != nil { + if err = WriteDataRefer(w, self.Refer); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} +func WalkDataInfo(w Walker, self *DataInfo) { + + w.Log("[DataInfo]") + w.Start() + if self.Refer != nil { + WalkDataRefer(w, self.Refer) + } + w.End() + return +} + +type DataRefer struct { + Version int + Flags int + Url *DataReferUrl +} + +func ReadDataRefer(r *io.LimitedReader) (res *DataRefer, err error) { + + self := &DataRefer{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if _, err = ReadDummy(r, 4); err != nil { + return + } + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "url ": + { + if self.Url, err = ReadDataReferUrl(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + res = self + return +} +func WriteDataRefer(w io.WriteSeeker, self *DataRefer) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "dref"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + var atomsCount int + var atomsCountPos int64 + if atomsCountPos, err = WriteEmptyInt(w, 4); err != nil { + return + } + if self.Url != nil { + if err = WriteDataReferUrl(w, self.Url); err != nil { + return + } + atomsCount++ + } + if err = RefillInt(w, atomsCountPos, atomsCount, 4); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} +func WalkDataRefer(w Walker, self *DataRefer) { + + w.Log("[DataRefer]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + if self.Url != nil { + WalkDataReferUrl(w, self.Url) + } + w.End() + return +} + +type DataReferUrl struct { + Version int + Flags int +} + +func ReadDataReferUrl(r *io.LimitedReader) (res *DataReferUrl, err error) { + + self := &DataReferUrl{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + res = self + return +} +func WriteDataReferUrl(w io.WriteSeeker, self *DataReferUrl) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "url "); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} +func WalkDataReferUrl(w Walker, self *DataReferUrl) { + + w.Log("[DataReferUrl]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.End() + return +} type SoundMediaInfo struct { Version int @@ -765,6 +1157,19 @@ func WriteSoundMediaInfo(w io.WriteSeeker, self *SoundMediaInfo) (err error) { } return } +func WalkSoundMediaInfo(w Walker, self *SoundMediaInfo) { + + w.Log("[SoundMediaInfo]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("Balance") + w.Int(self.Balance) + w.End() + return +} type VideoMediaInfo struct { Version int @@ -819,6 +1224,23 @@ func WriteVideoMediaInfo(w io.WriteSeeker, self *VideoMediaInfo) (err error) { } return } +func WalkVideoMediaInfo(w Walker, self *VideoMediaInfo) { + + w.Log("[VideoMediaInfo]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("GraphicsMode") + w.Int(self.GraphicsMode) + for _, item := range self.Opcolor { + w.Name("Opcolor") + w.Int(item) + } + w.End() + return +} type SampleTable struct { SampleDesc *SampleDesc @@ -938,6 +1360,34 @@ func WriteSampleTable(w io.WriteSeeker, self *SampleTable) (err error) { } return } +func WalkSampleTable(w Walker, self *SampleTable) { + + w.Log("[SampleTable]") + w.Start() + if self.SampleDesc != nil { + WalkSampleDesc(w, self.SampleDesc) + } + if self.TimeToSample != nil { + WalkTimeToSample(w, self.TimeToSample) + } + if self.CompositionOffset != nil { + WalkCompositionOffset(w, self.CompositionOffset) + } + if self.SampleToChunk != nil { + WalkSampleToChunk(w, self.SampleToChunk) + } + if self.SyncSample != nil { + WalkSyncSample(w, self.SyncSample) + } + if self.ChunkOffset != nil { + WalkChunkOffset(w, self.ChunkOffset) + } + if self.SampleSize != nil { + WalkSampleSize(w, self.SampleSize) + } + w.End() + return +} type SampleDesc struct { Version int @@ -1023,6 +1473,21 @@ func WriteSampleDesc(w io.WriteSeeker, self *SampleDesc) (err error) { } return } +func WalkSampleDesc(w Walker, self *SampleDesc) { + + w.Log("[SampleDesc]") + w.Start() + w.Name("Version") + w.Int(self.Version) + if self.Avc1Desc != nil { + WalkAvc1Desc(w, self.Avc1Desc) + } + if self.Mp4aDesc != nil { + WalkMp4aDesc(w, self.Mp4aDesc) + } + w.End() + return +} type Mp4aDesc struct { DataRefIdx int @@ -1138,6 +1603,32 @@ func WriteMp4aDesc(w io.WriteSeeker, self *Mp4aDesc) (err error) { } return } +func WalkMp4aDesc(w Walker, self *Mp4aDesc) { + + w.Log("[Mp4aDesc]") + w.Start() + w.Name("DataRefIdx") + w.Int(self.DataRefIdx) + w.Name("Version") + w.Int(self.Version) + w.Name("RevisionLevel") + w.Int(self.RevisionLevel) + w.Name("Vendor") + w.Int(self.Vendor) + w.Name("NumberOfChannels") + w.Int(self.NumberOfChannels) + w.Name("SampleSize") + w.Int(self.SampleSize) + w.Name("CompressionId") + w.Int(self.CompressionId) + w.Name("SampleRate") + w.Fixed(self.SampleRate) + if self.Conf != nil { + WalkElemStreamDesc(w, self.Conf) + } + w.End() + return +} type ElemStreamDesc struct { Version int @@ -1174,6 +1665,17 @@ func WriteElemStreamDesc(w io.WriteSeeker, self *ElemStreamDesc) (err error) { } return } +func WalkElemStreamDesc(w Walker, self *ElemStreamDesc) { + + w.Log("[ElemStreamDesc]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Data") + w.Bytes(self.Data) + w.End() + return +} type Avc1Desc struct { DataRefIdx int @@ -1331,6 +1833,44 @@ func WriteAvc1Desc(w io.WriteSeeker, self *Avc1Desc) (err error) { } return } +func WalkAvc1Desc(w Walker, self *Avc1Desc) { + + w.Log("[Avc1Desc]") + w.Start() + w.Name("DataRefIdx") + w.Int(self.DataRefIdx) + w.Name("Version") + w.Int(self.Version) + w.Name("Revision") + w.Int(self.Revision) + w.Name("Vendor") + w.Int(self.Vendor) + w.Name("TemporalQuality") + w.Int(self.TemporalQuality) + w.Name("SpatialQuality") + w.Int(self.SpatialQuality) + w.Name("Width") + w.Int(self.Width) + w.Name("Height") + w.Int(self.Height) + w.Name("HorizontalResolution") + w.Fixed(self.HorizontalResolution) + w.Name("VorizontalResolution") + w.Fixed(self.VorizontalResolution) + w.Name("FrameCount") + w.Int(self.FrameCount) + w.Name("CompressorName") + w.String(self.CompressorName) + w.Name("Depth") + w.Int(self.Depth) + w.Name("ColorTableId") + w.Int(self.ColorTableId) + if self.Conf != nil { + WalkAvc1Conf(w, self.Conf) + } + w.End() + return +} type Avc1Conf struct { Record AVCDecoderConfRecord @@ -1360,6 +1900,14 @@ func WriteAvc1Conf(w io.WriteSeeker, self *Avc1Conf) (err error) { } return } +func WalkAvc1Conf(w Walker, self *Avc1Conf) { + + w.Log("[Avc1Conf]") + w.Start() + WalkAVCDecoderConfRecord(w, self.Record) + w.End() + return +} type TimeToSample struct { Version int @@ -1415,6 +1963,20 @@ func WriteTimeToSample(w io.WriteSeeker, self *TimeToSample) (err error) { } return } +func WalkTimeToSample(w Walker, self *TimeToSample) { + + w.Log("[TimeToSample]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + for _, item := range self.Entries { + WalkTimeToSampleEntry(w, item) + } + w.End() + return +} type TimeToSampleEntry struct { Count int @@ -1441,6 +2003,17 @@ func WriteTimeToSampleEntry(w io.WriteSeeker, self TimeToSampleEntry) (err error } return } +func WalkTimeToSampleEntry(w Walker, self TimeToSampleEntry) { + + w.Log("[TimeToSampleEntry]") + w.Start() + w.Name("Count") + w.Int(self.Count) + w.Name("Duration") + w.Int(self.Duration) + w.End() + return +} type SampleToChunk struct { Version int @@ -1496,6 +2069,20 @@ func WriteSampleToChunk(w io.WriteSeeker, self *SampleToChunk) (err error) { } return } +func WalkSampleToChunk(w Walker, self *SampleToChunk) { + + w.Log("[SampleToChunk]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + for _, item := range self.Entries { + WalkSampleToChunkEntry(w, item) + } + w.End() + return +} type SampleToChunkEntry struct { FirstChunk int @@ -1529,6 +2116,19 @@ func WriteSampleToChunkEntry(w io.WriteSeeker, self SampleToChunkEntry) (err err } return } +func WalkSampleToChunkEntry(w Walker, self SampleToChunkEntry) { + + w.Log("[SampleToChunkEntry]") + w.Start() + w.Name("FirstChunk") + w.Int(self.FirstChunk) + w.Name("SamplesPerChunk") + w.Int(self.SamplesPerChunk) + w.Name("SampleDescId") + w.Int(self.SampleDescId) + w.End() + return +} type CompositionOffset struct { Version int @@ -1584,6 +2184,20 @@ func WriteCompositionOffset(w io.WriteSeeker, self *CompositionOffset) (err erro } return } +func WalkCompositionOffset(w Walker, self *CompositionOffset) { + + w.Log("[CompositionOffset]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + for _, item := range self.Entries { + WalkCompositionOffsetEntry(w, item) + } + w.End() + return +} type CompositionOffsetEntry struct { Count int @@ -1610,6 +2224,17 @@ func WriteCompositionOffsetEntry(w io.WriteSeeker, self CompositionOffsetEntry) } return } +func WalkCompositionOffsetEntry(w Walker, self CompositionOffsetEntry) { + + w.Log("[CompositionOffsetEntry]") + w.Start() + w.Name("Count") + w.Int(self.Count) + w.Name("Offset") + w.Int(self.Offset) + w.End() + return +} type SyncSample struct { Version int @@ -1665,6 +2290,21 @@ func WriteSyncSample(w io.WriteSeeker, self *SyncSample) (err error) { } return } +func WalkSyncSample(w Walker, self *SyncSample) { + + w.Log("[SyncSample]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + for _, item := range self.Entries { + w.Name("Entries") + w.Int(item) + } + w.End() + return +} type SampleSize struct { Version int @@ -1727,6 +2367,23 @@ func WriteSampleSize(w io.WriteSeeker, self *SampleSize) (err error) { } return } +func WalkSampleSize(w Walker, self *SampleSize) { + + w.Log("[SampleSize]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("SampleSize") + w.Int(self.SampleSize) + for _, item := range self.Entries { + w.Name("Entries") + w.Int(item) + } + w.End() + return +} type ChunkOffset struct { Version int @@ -1782,3 +2439,18 @@ func WriteChunkOffset(w io.WriteSeeker, self *ChunkOffset) (err error) { } return } +func WalkChunkOffset(w Walker, self *ChunkOffset) { + + w.Log("[ChunkOffset]") + w.Start() + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + for _, item := range self.Entries { + w.Name("Entries") + w.Int(item) + } + w.End() + return +} diff --git a/example/read.go b/example/read.go index 3848402..422d439 100644 --- a/example/read.go +++ b/example/read.go @@ -9,7 +9,7 @@ import ( func main() { testconv := flag.Bool("testconv", false, "") - testrewrite := flag.Bool("testrewrite", false, "") + probe := flag.Bool("probe", false, "") flag.Parse() if *testconv { @@ -19,8 +19,8 @@ func main() { } } - if *testrewrite { - if err := mp4.TestRewrite(flag.Arg(0)); err != nil { + if *probe { + if err := mp4.ProbeFile(flag.Arg(0)); err != nil { log.Println(err) return } diff --git a/test.go b/test.go new file mode 100644 index 0000000..9952d03 --- /dev/null +++ b/test.go @@ -0,0 +1,161 @@ + +package mp4 + +import ( + "github.com/nareix/mp4/atom" + "os" + "io" + "fmt" + "log" +) + +/* +func TestRewrite(filename string) (err error) { + var infile *os.File + if infile, err = os.Open(filename); err != nil { + return + } + + var outfile *os.File + if outfile, err = os.Open(filename+".out.mp4"); err != nil { + return + } + + return +} +*/ + +func ProbeFile(filename string) (err error) { + var osfile *os.File + if osfile, err = os.Open(filename); err != nil { + return + } + + var finfo os.FileInfo + if finfo, err = osfile.Stat(); err != nil { + return + } + + var moov *atom.Movie + + lr := &io.LimitedReader{R: osfile, N: finfo.Size()} + for lr.N > 0 { + var ar *io.LimitedReader + + var cc4 string + if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { + log.Println("read atom failed") + return + } + + if cc4 == "moov" { + if moov, err = atom.ReadMovie(ar); err != nil { + log.Println("read '%s' atom failed", cc4) + return + } + } + + if _, err = atom.ReadDummy(lr, int(ar.N)); err != nil { + return + } + } + + if moov == nil { + err = fmt.Errorf("'moov' atom not found") + log.Println("'moov' atom not found") + return + } + + dumper := &atom.Dumper{} + atom.WalkMovie(dumper, moov) + + return +} + +func TestConvert(filename string) (err error) { + var osfile *os.File + if osfile, err = os.Open(filename); err != nil { + return + } + + var finfo os.FileInfo + if finfo, err = osfile.Stat(); err != nil { + return + } + log.Println("filesize", finfo.Size()) + + lr := &io.LimitedReader{R: osfile, N: finfo.Size()} + + var outfile *os.File + if outfile, err = os.Create(filename+".out.mp4"); err != nil { + return + } + + for lr.N > 0 { + var ar *io.LimitedReader + + var cc4 string + if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { + return + } + + if cc4 == "moov" { + + curPos, _ := outfile.Seek(0, 1) + origSize := ar.N+8 + var moov *atom.Movie + if moov, err = atom.ReadMovie(ar); err != nil { + return + } + + //log.Println(moov.Tracks[0].Media.Info.Data.Refer) + + if err = atom.WriteMovie(outfile, moov); err != nil { + return + } + curPosAfterRead, _ := outfile.Seek(0, 1) + bytesWritten := curPosAfterRead - curPos + + log.Println("regen moov", "tracks nr", len(moov.Tracks), + "origSize", origSize, "bytesWritten", bytesWritten, + ) + + padSize := origSize - bytesWritten - 8 + aw, _ := atom.WriteAtomHeader(outfile, "free") + atom.WriteDummy(outfile, int(padSize)) + aw.Close() + + } else { + + outcc4 := cc4 + if outcc4 != "mdat" { + log.Println("omit", cc4) + outcc4 = "free" + } else { + log.Println("copy", cc4) + } + + var aw *atom.Writer + if aw, err = atom.WriteAtomHeader(outfile, outcc4); err != nil { + return + } + + if _, err = io.CopyN(aw, ar, ar.N); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + } + + //log.Println("atom", cc4, "left", lr.N) + //atom.ReadDummy(ar, int(ar.N)) + } + + if err = outfile.Close(); err != nil { + return + } + + return +} + diff --git a/writer.go b/writer.go index 28f7355..e58e701 100644 --- a/writer.go +++ b/writer.go @@ -170,6 +170,13 @@ func (self *SimpleH264Writer) Finish() (err error) { Flags: 0x000001, }, Sample: self.sample, + Data: &atom.DataInfo{ + Refer: &atom.DataRefer{ + Url: &atom.DataReferUrl{ + Flags: 0x000001, // Self reference + }, + }, + }, }, Handler: &atom.HandlerRefer{ SubType: "vide", From 8005e55dcb3ef3006dc5c5446421d96d36a77e70 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 27 Nov 2015 15:41:37 +0800 Subject: [PATCH 22/93] add Width and Height params to SimpleH264Writer --- test.go | 7 ++++--- writer.go | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/test.go b/test.go index 9952d03..bd731b0 100644 --- a/test.go +++ b/test.go @@ -36,6 +36,7 @@ func ProbeFile(filename string) (err error) { return } + dumper := &atom.Dumper{} var moov *atom.Movie lr := &io.LimitedReader{R: osfile, N: finfo.Size()} @@ -53,6 +54,9 @@ func ProbeFile(filename string) (err error) { log.Println("read '%s' atom failed", cc4) return } + atom.WalkMovie(dumper, moov) + } else { + fmt.Println("atom:", cc4) } if _, err = atom.ReadDummy(lr, int(ar.N)); err != nil { @@ -66,9 +70,6 @@ func ProbeFile(filename string) (err error) { return } - dumper := &atom.Dumper{} - atom.WalkMovie(dumper, moov) - return } diff --git a/writer.go b/writer.go index e58e701..76f6a46 100644 --- a/writer.go +++ b/writer.go @@ -13,6 +13,9 @@ type SimpleH264Writer struct { SPS []byte PPS []byte + Width int + Height int + duration int sample *atom.SampleTable @@ -33,6 +36,14 @@ func (self *SimpleH264Writer) prepare() (err error) { self.sample = &atom.SampleTable{ SampleDesc: &atom.SampleDesc{ Avc1Desc: &atom.Avc1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: self.Width, + Height: self.Height, + FrameCount: 1, + Depth: 24, + ColorTableId: -1, Conf: &atom.Avc1Conf{}, }, }, @@ -149,15 +160,18 @@ func (self *SimpleH264Writer) Finish() (err error) { PreferredRate: atom.IntToFixed(1), PreferredVolume: atom.IntToFixed(1), Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + NextTrackId: 2, } track := &atom.Track{ Header: &atom.TrackHeader{ - Flags: 0x0001, // enabled + TrackId: 1, + Flags: 0x0003, // Track enabled | Track in movie Duration: self.duration, Volume: atom.IntToFixed(1), Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - TrackId: 1, + TrackWidth: atom.IntToFixed(self.Width), + TrackHeight: atom.IntToFixed(self.Height), }, Media: &atom.Media{ From a860b0a997a9239f703baf19248eff35a8c813f0 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 28 Nov 2015 00:36:14 +0800 Subject: [PATCH 23/93] add h264sps parser to get width and height --- atom/otherStruct.go | 303 +++++++++++++++++++++++++++++++++++++++++--- test.go | 40 +++++- writer.go | 9 ++ 3 files changed, 335 insertions(+), 17 deletions(-) diff --git a/atom/otherStruct.go b/atom/otherStruct.go index de77e22..ec73293 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -4,15 +4,285 @@ package atom import ( "io" "fmt" + "bytes" ) +type GolombBitReader struct { + R io.Reader + buf [1]byte + left byte +} + +func (self *GolombBitReader) ReadBit() (res uint, err error) { + if self.left == 0 { + if _, err = self.R.Read(self.buf[:]); err != nil { + return + } + self.left = 8 + } + self.left-- + res = uint(self.buf[0]>>self.left)&1 + return +} + +func (self *GolombBitReader) ReadBits(n int) (res uint, err error) { + for i := 0; i < n; i++ { + var bit uint + if bit, err = self.ReadBit(); err != nil { + return + } + res |= bit< 0 { + track := moov.Tracks[0] + record := getAVCDecoderConfRecordByTrack(track) + + if record != nil && len(record.SPS) > 0 { + sps := record.SPS[0] + if len(sps) > 1 { + sps = sps[1:] + log.Println(hex.Dump(sps)) + var info *atom.H264SPSInfo + if info, err = atom.ParseH264SPS(sps); err != nil { + return + } + log.Println(info) + } + } + } + return } diff --git a/writer.go b/writer.go index 76f6a46..86b672d 100644 --- a/writer.go +++ b/writer.go @@ -153,6 +153,15 @@ func (self *SimpleH264Writer) Finish() (err error) { return } + if self.Width == 0 || self.Height == 0 { + var info *atom.H264SPSInfo + if info, err = atom.ParseH264SPS(self.SPS); err != nil { + return + } + self.Width = int(info.Width) + self.Height = int(info.Height) + } + moov := &atom.Movie{} moov.Header = &atom.MovieHeader{ TimeScale: self.TimeScale, From b69852c281a6d696e6eb5e5f588509fd8fca2944 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 28 Nov 2015 00:43:49 +0800 Subject: [PATCH 24/93] fix sps parse bug in SimpleH264Writer prepare() --- writer.go | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/writer.go b/writer.go index 86b672d..89f8702 100644 --- a/writer.go +++ b/writer.go @@ -4,6 +4,7 @@ package mp4 import ( "github.com/nareix/mp4/atom" "io" + "fmt" ) type SimpleH264Writer struct { @@ -31,6 +32,25 @@ func (self *SimpleH264Writer) prepare() (err error) { return } + if len(self.SPS) == 0 { + err = fmt.Errorf("invalid SPS") + return + } + + if len(self.PPS) == 0 { + err = fmt.Errorf("invalid PPS") + return + } + + if self.Width == 0 || self.Height == 0 { + var info *atom.H264SPSInfo + if info, err = atom.ParseH264SPS(self.SPS[1:]); err != nil { + return + } + self.Width = int(info.Width) + self.Height = int(info.Height) + } + self.sampleIdx = 1 self.sample = &atom.SampleTable{ @@ -153,15 +173,6 @@ func (self *SimpleH264Writer) Finish() (err error) { return } - if self.Width == 0 || self.Height == 0 { - var info *atom.H264SPSInfo - if info, err = atom.ParseH264SPS(self.SPS); err != nil { - return - } - self.Width = int(info.Width) - self.Height = int(info.Height) - } - moov := &atom.Movie{} moov.Header = &atom.MovieHeader{ TimeScale: self.TimeScale, From e060a3e79dbc47d600aa509e6eb15c6d4ef3e02a Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 18 Mar 2016 19:57:00 +0800 Subject: [PATCH 25/93] add demuxer.go --- demuxer.go | 212 +++++++++++++++++++++++++++++++++++++++++++++ example/example.go | 26 ++++++ example/read.go | 30 ------- test.go | 200 ------------------------------------------ 4 files changed, 238 insertions(+), 230 deletions(-) create mode 100644 demuxer.go create mode 100644 example/example.go delete mode 100644 example/read.go delete mode 100644 test.go diff --git a/demuxer.go b/demuxer.go new file mode 100644 index 0000000..c1d0984 --- /dev/null +++ b/demuxer.go @@ -0,0 +1,212 @@ + +package mp4 + +import ( + "github.com/nareix/mp4/atom" + _ "os" + "fmt" + "io" + _ "log" +) + +type Demuxer struct { + R io.ReadSeeker + Tracks []*Track + TrackH264 *Track + TrackAAC *Track + MovieAtom *atom.Movie +} + +func (self *Demuxer) ReadHeader() (err error) { + var N int64 + var moov *atom.Movie + + if N, err = self.R.Seek(0, 2); err != nil { + return + } + if _, err = self.R.Seek(0, 0); err != nil { + return + } + + lr := &io.LimitedReader{R: self.R, N: N} + for lr.N > 0 { + var ar *io.LimitedReader + + var cc4 string + if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { + return + } + + if cc4 == "moov" { + if moov, err = atom.ReadMovie(ar); err != nil { + return + } + } + + if _, err = atom.ReadDummy(lr, int(ar.N)); err != nil { + return + } + } + + if moov == nil { + err = fmt.Errorf("'moov' atom not found") + return + } + self.MovieAtom = moov + + self.Tracks = []*Track{} + for _, atrack := range(moov.Tracks) { + track := &Track{ + TrackAtom: atrack, + r: self.R, + } + if atrack.Media != nil && atrack.Media.Info != nil && atrack.Media.Info.Sample != nil { + track.sample = atrack.Media.Info.Sample + } else { + err = fmt.Errorf("sample table not found") + return + } + if record := atom.GetAVCDecoderConfRecordByTrack(atrack); record != nil { + track.Type = H264 + self.TrackH264 = track + if len(record.PPS) > 0 { + track.PPS = record.PPS[0] + } + if len(record.SPS) > 0 { + track.SPS = record.SPS[0] + } + self.Tracks = append(self.Tracks, track) + } else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil { + self.TrackAAC = track + track.Type = AAC + self.Tracks = append(self.Tracks, track) + } + } + + return +} + +func (self *Track) setSampleIndex(index int) (err error) { + found := false + start := 0 + self.chunkGroupIndex = 0 + + for self.chunkIndex = range(self.sample.ChunkOffset.Entries) { + n := self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk + if index >= start && index < start+n { + found = true + self.sampleIndexInChunk = index-start + break + } + start += n + if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) && + self.chunkIndex+1 == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk { + self.chunkGroupIndex++ + } + } + if !found { + err = io.EOF + return + } + + self.sampleIndex = index + return +} + +func (self *Track) incSampleIndex() { + self.sampleIndexInChunk++ + if self.sampleIndexInChunk == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk { + self.chunkIndex++ + self.sampleIndexInChunk = 0 + } + if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) && + self.chunkIndex+1 == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk { + self.chunkGroupIndex++ + } + self.sampleIndex++ +} + +func (self *Track) SampleCount() int { + if self.sample.SampleSize.SampleSize == 0 { + chunkGroupIndex := 0 + count := 0 + for chunkIndex := range(self.sample.ChunkOffset.Entries) { + n := self.sample.SampleToChunk.Entries[chunkGroupIndex].SamplesPerChunk + count += n + if chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) && + chunkIndex+1 == self.sample.SampleToChunk.Entries[chunkGroupIndex+1].FirstChunk { + chunkGroupIndex++ + } + } + return count + } else { + return len(self.sample.SampleSize.Entries) + } +} + +func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { + return +} + +func (self *Track) ReadSampleAtIndex(index int) (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { + if self.sampleIndex+1 == index { + self.incSampleIndex() + } else if self.sampleIndex != index { + if err = self.setSampleIndex(index); err != nil { + return + } + } + + if self.chunkIndex > len(self.sample.ChunkOffset.Entries) { + err = io.EOF + return + } + if self.chunkGroupIndex >= len(self.sample.SampleToChunk.Entries) { + err = io.EOF + return + } + + chunkOffset := self.sample.ChunkOffset.Entries[self.chunkIndex] + sampleOffset := 0 + sampleSize := 0 + + if self.sample.SampleSize.SampleSize != 0 { + sampleOffset = chunkOffset + self.sampleIndexInChunk*self.sample.SampleSize.SampleSize + } else { + sampleOffset = chunkOffset + for i := self.sampleIndex-self.sampleIndexInChunk; i < self.sampleIndex; i++ { + sampleOffset += self.sample.SampleSize.Entries[i] + } + } + + if _, err = self.r.Seek(int64(sampleOffset), 0); err != nil { + return + } + data = make([]byte, sampleSize) + if _, err = self.r.Read(data); err != nil { + return + } + + return +} + +func (self *Track) Duration() float32 { + total := int64(0) + for _, entry := range(self.sample.TimeToSample.Entries) { + total += int64(entry.Duration*entry.Count) + } + return float32(total)/float32(self.TrackAtom.Media.Header.TimeScale) +} + +func (self *Track) TimeToSampleIndex(second float32) int { + return 0 +} + +func (self *Track) TimeStampToTime(ts int64) float32 { + return 0.0 +} + +func (self *Track) WriteSample(pts int64, dts int64, data []byte) (err error) { + return +} + diff --git a/example/example.go b/example/example.go new file mode 100644 index 0000000..c061d01 --- /dev/null +++ b/example/example.go @@ -0,0 +1,26 @@ + +package main + +import ( + "github.com/nareix/mp4" + "os" + "fmt" +) + +func DemuxExample() { + file, _ := os.Open("test.mp4") + + demuxer := &mp4.Demuxer{ + R: file, + } + demuxer.ReadHeader() + + fmt.Println("Duration: ", demuxer.TrackH264.Duration()) + count := demuxer.TrackH264.SampleCount() + fmt.Println("SampleCount: ", count) +} + +func main() { + DemuxExample() +} + diff --git a/example/read.go b/example/read.go deleted file mode 100644 index 422d439..0000000 --- a/example/read.go +++ /dev/null @@ -1,30 +0,0 @@ - -package main - -import ( - mp4 "./.." - "flag" - "log" -) - -func main() { - testconv := flag.Bool("testconv", false, "") - probe := flag.Bool("probe", false, "") - flag.Parse() - - if *testconv { - if err := mp4.TestConvert(flag.Arg(0)); err != nil { - log.Println(err) - return - } - } - - if *probe { - if err := mp4.ProbeFile(flag.Arg(0)); err != nil { - log.Println(err) - return - } - } - -} - diff --git a/test.go b/test.go deleted file mode 100644 index c89f626..0000000 --- a/test.go +++ /dev/null @@ -1,200 +0,0 @@ - -package mp4 - -import ( - "github.com/nareix/mp4/atom" - "os" - "io" - "fmt" - "log" - "encoding/hex" -) - -/* -func TestRewrite(filename string) (err error) { - var infile *os.File - if infile, err = os.Open(filename); err != nil { - return - } - - var outfile *os.File - if outfile, err = os.Open(filename+".out.mp4"); err != nil { - return - } - - return -} -*/ - -func getAVCDecoderConfRecordByTrack(track *atom.Track) (record *atom.AVCDecoderConfRecord) { - if media := track.Media; media != nil { - if info := media.Info; info != nil { - if sample := info.Sample; sample != nil { - if desc := sample.SampleDesc; desc != nil { - if avc1 := desc.Avc1Desc; avc1 != nil { - if conf := avc1.Conf; conf != nil { - return &conf.Record - } - } - } - } - } - } - return -} - -func ProbeFile(filename string) (err error) { - var osfile *os.File - if osfile, err = os.Open(filename); err != nil { - return - } - - var finfo os.FileInfo - if finfo, err = osfile.Stat(); err != nil { - return - } - - dumper := &atom.Dumper{} - var moov *atom.Movie - - lr := &io.LimitedReader{R: osfile, N: finfo.Size()} - for lr.N > 0 { - var ar *io.LimitedReader - - var cc4 string - if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { - log.Println("read atom failed") - return - } - - if cc4 == "moov" { - if moov, err = atom.ReadMovie(ar); err != nil { - log.Println("read '%s' atom failed", cc4) - return - } - if false { - atom.WalkMovie(dumper, moov) - } - } else { - fmt.Println("atom:", cc4) - } - - if _, err = atom.ReadDummy(lr, int(ar.N)); err != nil { - return - } - } - - if moov == nil { - err = fmt.Errorf("'moov' atom not found") - log.Println("'moov' atom not found") - return - } - - if len(moov.Tracks) > 0 { - track := moov.Tracks[0] - record := getAVCDecoderConfRecordByTrack(track) - - if record != nil && len(record.SPS) > 0 { - sps := record.SPS[0] - if len(sps) > 1 { - sps = sps[1:] - log.Println(hex.Dump(sps)) - var info *atom.H264SPSInfo - if info, err = atom.ParseH264SPS(sps); err != nil { - return - } - log.Println(info) - } - } - } - - return -} - -func TestConvert(filename string) (err error) { - var osfile *os.File - if osfile, err = os.Open(filename); err != nil { - return - } - - var finfo os.FileInfo - if finfo, err = osfile.Stat(); err != nil { - return - } - log.Println("filesize", finfo.Size()) - - lr := &io.LimitedReader{R: osfile, N: finfo.Size()} - - var outfile *os.File - if outfile, err = os.Create(filename+".out.mp4"); err != nil { - return - } - - for lr.N > 0 { - var ar *io.LimitedReader - - var cc4 string - if ar, cc4, err = atom.ReadAtomHeader(lr, ""); err != nil { - return - } - - if cc4 == "moov" { - - curPos, _ := outfile.Seek(0, 1) - origSize := ar.N+8 - var moov *atom.Movie - if moov, err = atom.ReadMovie(ar); err != nil { - return - } - - //log.Println(moov.Tracks[0].Media.Info.Data.Refer) - - if err = atom.WriteMovie(outfile, moov); err != nil { - return - } - curPosAfterRead, _ := outfile.Seek(0, 1) - bytesWritten := curPosAfterRead - curPos - - log.Println("regen moov", "tracks nr", len(moov.Tracks), - "origSize", origSize, "bytesWritten", bytesWritten, - ) - - padSize := origSize - bytesWritten - 8 - aw, _ := atom.WriteAtomHeader(outfile, "free") - atom.WriteDummy(outfile, int(padSize)) - aw.Close() - - } else { - - outcc4 := cc4 - if outcc4 != "mdat" { - log.Println("omit", cc4) - outcc4 = "free" - } else { - log.Println("copy", cc4) - } - - var aw *atom.Writer - if aw, err = atom.WriteAtomHeader(outfile, outcc4); err != nil { - return - } - - if _, err = io.CopyN(aw, ar, ar.N); err != nil { - return - } - if err = aw.Close(); err != nil { - return - } - } - - //log.Println("atom", cc4, "left", lr.N) - //atom.ReadDummy(ar, int(ar.N)) - } - - if err = outfile.Close(); err != nil { - return - } - - return -} - From 7f98b6983417ae3162f4ccffdf5d26365ac51b3f Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 18 Mar 2016 20:00:20 +0800 Subject: [PATCH 26/93] add track.go --- track.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 track.go diff --git a/track.go b/track.go new file mode 100644 index 0000000..e4bb9b9 --- /dev/null +++ b/track.go @@ -0,0 +1,27 @@ + +package mp4 + +import ( + "github.com/nareix/mp4/atom" + "io" +) + +const ( + H264 = 1 + AAC = 2 +) + +type Track struct { + Type int + SPS []byte + PPS []byte + TrackAtom *atom.Track + r io.ReadSeeker + + sample *atom.SampleTable + sampleIndex int + chunkGroupIndex int + chunkIndex int + sampleIndexInChunk int +} + From 21888f54d77d5a039f68cbe6e7746a11b4b26832 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 18 Mar 2016 20:11:54 +0800 Subject: [PATCH 27/93] add track.go --- demuxer.go | 34 ++++++++++++++++++++++++++++++++++ track.go | 7 +++++++ 2 files changed, 41 insertions(+) diff --git a/demuxer.go b/demuxer.go index c1d0984..bf4b5c6 100644 --- a/demuxer.go +++ b/demuxer.go @@ -109,6 +109,40 @@ func (self *Track) setSampleIndex(index int) (err error) { return } + start = 0 + found = false + self.ptsEntryIndex = 0 + for self.ptsEntryIndex < len(self.sample.TimeToSample.Entries) { + n := self.sample.TimeToSample.Entries[self.ptsEntryIndex].Count + if index >= start && index < start+n { + self.sampleIndexInPtsEntry = index-start + break + } + start += n + self.ptsEntryIndex++ + } + if !found { + err = io.EOF + return + } + + start = 0 + found = false + self.dtsEntryIndex = 0 + for self.dtsEntryIndex < len(self.sample.CompositionOffset.Entries) { + n := self.sample.CompositionOffset.Entries[self.dtsEntryIndex].Count + if index >= start && index < start+n { + self.sampleIndexInDtsEntry = index-start + break + } + start += n + self.dtsEntryIndex++ + } + if !found { + err = io.EOF + return + } + self.sampleIndex = index return } diff --git a/track.go b/track.go index e4bb9b9..b68e0e1 100644 --- a/track.go +++ b/track.go @@ -20,6 +20,13 @@ type Track struct { sample *atom.SampleTable sampleIndex int + + ptsEntryIndex int + sampleIndexInPtsEntry int + + dtsEntryIndex int + sampleIndexInDtsEntry int + chunkGroupIndex int chunkIndex int sampleIndexInChunk int From ae08044e8d4f5d6d6ec444bf54271af695d19dec Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 19 Mar 2016 15:07:55 +0800 Subject: [PATCH 28/93] demuxer complete --- atom/utils.go | 35 ++++++ demuxer.go | 244 +++++++++++++++++++++++++++++++++--------- example/example.go | 39 ++++++- writer.go => muxer.go | 0 track.go | 12 ++- 5 files changed, 275 insertions(+), 55 deletions(-) create mode 100644 atom/utils.go rename writer.go => muxer.go (100%) diff --git a/atom/utils.go b/atom/utils.go new file mode 100644 index 0000000..5e7e792 --- /dev/null +++ b/atom/utils.go @@ -0,0 +1,35 @@ + +package atom + +func GetAVCDecoderConfRecordByTrack(track *Track) (record *AVCDecoderConfRecord) { + if media := track.Media; media != nil { + if info := media.Info; info != nil { + if sample := info.Sample; sample != nil { + if desc := sample.SampleDesc; desc != nil { + if avc1 := desc.Avc1Desc; avc1 != nil { + if conf := avc1.Conf; conf != nil { + return &conf.Record + } + } + } + } + } + } + return +} + +func GetMp4aDescByTrack(track *Track) (mp4a *Mp4aDesc) { + if media := track.Media; media != nil { + if info := media.Info; info != nil { + if sample := info.Sample; sample != nil { + if desc := sample.SampleDesc; desc != nil { + if mp4a = desc.Mp4aDesc; mp4a != nil { + return + } + } + } + } + } + return +} + diff --git a/demuxer.go b/demuxer.go index bf4b5c6..5741009 100644 --- a/demuxer.go +++ b/demuxer.go @@ -3,10 +3,8 @@ package mp4 import ( "github.com/nareix/mp4/atom" - _ "os" "fmt" "io" - _ "log" ) type Demuxer struct { @@ -109,54 +107,143 @@ func (self *Track) setSampleIndex(index int) (err error) { return } + if self.sample.SampleSize.SampleSize != 0 { + self.sampleOffsetInChunk = int64(self.sampleIndexInChunk*self.sample.SampleSize.SampleSize) + } else { + if index >= len(self.sample.SampleSize.Entries) { + err = io.EOF + return + } + self.sampleOffsetInChunk = int64(0) + for i := index-self.sampleIndexInChunk; i < index; i++ { + self.sampleOffsetInChunk += int64(self.sample.SampleSize.Entries[i]) + } + } + + self.dts = int64(0) start = 0 found = false - self.ptsEntryIndex = 0 - for self.ptsEntryIndex < len(self.sample.TimeToSample.Entries) { - n := self.sample.TimeToSample.Entries[self.ptsEntryIndex].Count + self.sttsEntryIndex = 0 + for self.sttsEntryIndex < len(self.sample.TimeToSample.Entries) { + entry := self.sample.TimeToSample.Entries[self.sttsEntryIndex] + n := entry.Count if index >= start && index < start+n { - self.sampleIndexInPtsEntry = index-start + self.sampleIndexInSttsEntry = index-start + self.dts += int64((index-start)*entry.Duration) break } start += n - self.ptsEntryIndex++ + self.dts += int64(n*entry.Duration) + self.sttsEntryIndex++ } if !found { err = io.EOF return } - start = 0 - found = false - self.dtsEntryIndex = 0 - for self.dtsEntryIndex < len(self.sample.CompositionOffset.Entries) { - n := self.sample.CompositionOffset.Entries[self.dtsEntryIndex].Count - if index >= start && index < start+n { - self.sampleIndexInDtsEntry = index-start - break + if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 { + start = 0 + found = false + self.cttsEntryIndex = 0 + for self.cttsEntryIndex < len(self.sample.CompositionOffset.Entries) { + n := self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count + if index >= start && index < start+n { + self.sampleIndexInCttsEntry = index-start + break + } + start += n + self.cttsEntryIndex++ + } + if !found { + err = io.EOF + return } - start += n - self.dtsEntryIndex++ } - if !found { - err = io.EOF - return + + if self.sample.SyncSample != nil { + self.syncSampleIndex = 0 + for self.syncSampleIndex < len(self.sample.SyncSample.Entries)-1 { + if self.sample.SyncSample.Entries[self.syncSampleIndex+1]-1 > index { + break + } + self.syncSampleIndex++ + } } self.sampleIndex = index return } +func (self *Track) isSampleValid() bool { + if self.chunkIndex >= len(self.sample.ChunkOffset.Entries) { + return false + } + if self.chunkGroupIndex >= len(self.sample.SampleToChunk.Entries) { + return false + } + if self.sttsEntryIndex >= len(self.sample.TimeToSample.Entries) { + return false + } + if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 { + if self.cttsEntryIndex >= len(self.sample.CompositionOffset.Entries) { + return false + } + } + if self.sample.SyncSample != nil { + if self.syncSampleIndex >= len(self.sample.SyncSample.Entries) { + return false + } + } + if self.sample.SampleSize.SampleSize != 0 { + if self.sampleIndex >= len(self.sample.SampleSize.Entries) { + return false + } + } + return true +} + func (self *Track) incSampleIndex() { self.sampleIndexInChunk++ if self.sampleIndexInChunk == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk { self.chunkIndex++ self.sampleIndexInChunk = 0 + self.sampleOffsetInChunk = int64(0) + } else { + if self.sample.SampleSize.SampleSize != 0 { + self.sampleOffsetInChunk += int64(self.sample.SampleSize.SampleSize) + } else { + self.sampleOffsetInChunk += int64(self.sample.SampleSize.Entries[self.sampleIndex]) + } } + if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) && self.chunkIndex+1 == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk { self.chunkGroupIndex++ } + + sttsEntry := self.sample.TimeToSample.Entries[self.sttsEntryIndex] + self.sampleIndexInSttsEntry++ + self.dts += int64(sttsEntry.Duration) + if self.sampleIndexInSttsEntry == sttsEntry.Count { + self.sampleIndexInSttsEntry = 0 + self.sttsEntryIndex++ + } + + if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 { + self.sampleIndexInCttsEntry++ + if self.sampleIndexInCttsEntry == self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count { + self.sampleIndexInCttsEntry = 0 + self.cttsEntryIndex++ + } + } + + if self.sample.SyncSample != nil { + entries := self.sample.SyncSample.Entries + if self.syncSampleIndex+1 < len(entries) && entries[self.syncSampleIndex+1]-1 == self.sampleIndex+1 { + self.syncSampleIndex++ + } + } + self.sampleIndex++ } @@ -179,65 +266,122 @@ func (self *Track) SampleCount() int { } func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { - return -} - -func (self *Track) ReadSampleAtIndex(index int) (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { - if self.sampleIndex+1 == index { - self.incSampleIndex() - } else if self.sampleIndex != index { - if err = self.setSampleIndex(index); err != nil { - return - } - } - - if self.chunkIndex > len(self.sample.ChunkOffset.Entries) { - err = io.EOF - return - } - if self.chunkGroupIndex >= len(self.sample.SampleToChunk.Entries) { + if !self.isSampleValid() { err = io.EOF return } chunkOffset := self.sample.ChunkOffset.Entries[self.chunkIndex] - sampleOffset := 0 sampleSize := 0 - if self.sample.SampleSize.SampleSize != 0 { - sampleOffset = chunkOffset + self.sampleIndexInChunk*self.sample.SampleSize.SampleSize + sampleSize = self.sample.SampleSize.SampleSize } else { - sampleOffset = chunkOffset - for i := self.sampleIndex-self.sampleIndexInChunk; i < self.sampleIndex; i++ { - sampleOffset += self.sample.SampleSize.Entries[i] - } + sampleSize = self.sample.SampleSize.Entries[self.sampleIndex] } + sampleOffset := int64(chunkOffset)+self.sampleOffsetInChunk if _, err = self.r.Seek(int64(sampleOffset), 0); err != nil { return } + data = make([]byte, sampleSize) if _, err = self.r.Read(data); err != nil { return } + if self.sample.SyncSample != nil { + if self.sample.SyncSample.Entries[self.syncSampleIndex]-1 == self.sampleIndex { + isKeyFrame = true + } + } + + //println("pts/dts", self.ptsEntryIndex, self.dtsEntryIndex) + dts = self.dts + if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 { + pts = self.dts+int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset) + } else { + pts = dts + } + + self.incSampleIndex() return } -func (self *Track) Duration() float32 { +func (self *Track) Duration() float64 { total := int64(0) for _, entry := range(self.sample.TimeToSample.Entries) { total += int64(entry.Duration*entry.Count) } - return float32(total)/float32(self.TrackAtom.Media.Header.TimeScale) + return float64(total)/float64(self.TrackAtom.Media.Header.TimeScale) } -func (self *Track) TimeToSampleIndex(second float32) int { - return 0 +func (self *Track) CurTime() float64 { + return self.TimeStampToTime(self.dts) } -func (self *Track) TimeStampToTime(ts int64) float32 { - return 0.0 +func (self *Track) CurTimeStamp() int64 { + return self.dts +} + +func (self *Track) CurSampleIndex() int { + return self.sampleIndex +} + +func (self *Track) SeekToTime(time float64) error { + index := self.TimeToSampleIndex(time) + return self.setSampleIndex(index) +} + +func (self *Track) SeekToSampleIndex(index int) error { + return self.setSampleIndex(index) +} + +func (self *Track) TimeToSampleIndex(time float64) int { + targetTs := self.TimeToTimeStamp(time) + targetIndex := 0 + + startTs := int64(0) + endTs := int64(0) + startIndex := 0 + endIndex := 0 + found := false + for _, entry := range(self.sample.TimeToSample.Entries) { + endTs = startTs+int64(entry.Count*entry.Duration) + endIndex = startIndex+entry.Count + if targetTs >= startTs && targetTs < endTs { + targetIndex = startIndex+int((targetTs-startTs)/int64(entry.Duration)) + found = true + } + startTs = endTs + startIndex = endIndex + } + if !found { + if targetTs < 0 { + targetIndex = 0 + } else { + targetIndex = endIndex-1 + } + } + + if self.sample.SyncSample != nil { + entries := self.sample.SyncSample.Entries + for i := len(entries)-1; i >= 0; i-- { + if entries[i]-1 < targetIndex { + targetIndex = entries[i]-1 + break + } + } + } + + return targetIndex +} + +func (self *Track) TimeToTimeStamp(time float64) int64 { + return int64(time*float64(self.TrackAtom.Media.Header.TimeScale)) +} + +func (self *Track) TimeStampToTime(ts int64) float64 { + return float64(ts)/float64(self.TrackAtom.Media.Header.TimeScale) } func (self *Track) WriteSample(pts int64, dts int64, data []byte) (err error) { diff --git a/example/example.go b/example/example.go index c061d01..3b0e1e2 100644 --- a/example/example.go +++ b/example/example.go @@ -5,19 +5,56 @@ import ( "github.com/nareix/mp4" "os" "fmt" + "encoding/hex" ) func DemuxExample() { file, _ := os.Open("test.mp4") - demuxer := &mp4.Demuxer{ R: file, } demuxer.ReadHeader() + fmt.Println("Total tracks: ", len(demuxer.Tracks)) fmt.Println("Duration: ", demuxer.TrackH264.Duration()) + count := demuxer.TrackH264.SampleCount() fmt.Println("SampleCount: ", count) + + demuxer.TrackH264.SeekToTime(2.3) + + var sample []byte + for i := 0; i < 5; i++ { + pts, dts, isKeyFrame, data, err := demuxer.TrackH264.ReadSample() + fmt.Println("sample #", + i, pts, dts, isKeyFrame, len(data), + demuxer.TrackH264.CurTime(), + err, + ) + if i == 3 { + sample = data + } + } + fmt.Println("Sample H264 frame:") + fmt.Print(hex.Dump(sample)) + + fmt.Println("Duration(AAC): ", demuxer.TrackAAC.Duration()) + fmt.Println("SampleCount(AAC): ", demuxer.TrackAAC.SampleCount()) + demuxer.TrackAAC.SeekToTime(1.3) + + for i := 0; i < 5; i++ { + pts, dts, isKeyFrame, data, err := demuxer.TrackAAC.ReadSample() + fmt.Println("sample(AAC) #", + i, pts, dts, isKeyFrame, len(data), + demuxer.TrackAAC.CurTime(), + err, + ) + if i == 1 { + sample = data + } + } + fmt.Println("Sample AAC frame:") + fmt.Print(hex.Dump(sample)) } func main() { diff --git a/writer.go b/muxer.go similarity index 100% rename from writer.go rename to muxer.go diff --git a/track.go b/track.go index b68e0e1..87f40f8 100644 --- a/track.go +++ b/track.go @@ -21,11 +21,15 @@ type Track struct { sample *atom.SampleTable sampleIndex int - ptsEntryIndex int - sampleIndexInPtsEntry int + sampleOffsetInChunk int64 + syncSampleIndex int - dtsEntryIndex int - sampleIndexInDtsEntry int + dts int64 + sttsEntryIndex int + sampleIndexInSttsEntry int + + cttsEntryIndex int + sampleIndexInCttsEntry int chunkGroupIndex int chunkIndex int From 69a85bd2321a9dc4f2994ab3b73cc29b8959fd6f Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 19 Mar 2016 15:08:33 +0800 Subject: [PATCH 29/93] add README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..a8919aa --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# mp4 muxer written in go + +Includes h264/aac track muxer, mp4 atoms reader/writer/dumper + +# Examples + From 917cb5112f690b63728d8196220abe412cedd9f0 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 19 Mar 2016 15:10:08 +0800 Subject: [PATCH 30/93] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a8919aa..712d2f3 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,4 @@ Includes h264/aac track muxer, mp4 atoms reader/writer/dumper # Examples +[Demuxer example](https://github.com/nareix/mp4/blob/master/example/example.go#L11) From 990565736830628a4fc8a5d54cda5af9c0901ab2 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 19 Mar 2016 15:12:02 +0800 Subject: [PATCH 31/93] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 712d2f3..bd670b3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# mp4 muxer written in go +# Golang mp4 muxer/demuxer Includes h264/aac track muxer, mp4 atoms reader/writer/dumper From 6da165e1853376a9bffcea945aaff3b89c051b80 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 19 Mar 2016 15:46:35 +0800 Subject: [PATCH 32/93] Update README.md --- README.md | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bd670b3..2c3abd0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,42 @@ -# Golang mp4 muxer/demuxer +# A pure golang mp4 library -Includes h264/aac track muxer, mp4 atoms reader/writer/dumper +Provides mp4 reader/writer and mp4 atom manipulations functions. -# Examples +Open a mp4 file and read the first sample: +``` +file, _ := os.Open("test.mp4") +demuxer := &mp4.Demuxer{R: file} +demuxer.ReadHeader() +pts, dts, isKeyFrame, data, err := demuxer.TrackH264.ReadSample() +``` + +do some seeking: + +``` +demuxer.TrackH264.SeekToTime(2.0) +``` + +demuxer demo code [here](https://github.com/nareix/mp4/blob/master/example/example.go#L11) + +the library also provide atom struct decoding/encoding functions( +learn more about mp4 atoms [here](https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html) +) + +you can access atom structs via `Demuxer.TrackH264.TrackAtom`. for example: + +``` +// Get the raw TimeScale field inside `mvhd` atom +fmt.Println(demuxer.TrackH264.TrackAtom.Media.Header.TimeScale) +``` + +for more see Atom API Docs + +# Documentation + +[API Docs](https://godoc.org/github.com/nareix/mp4) + +[Atom API Docs](https://godoc.org/github.com/nareix/mp4/atom) + + +## Atoms manipulation -[Demuxer example](https://github.com/nareix/mp4/blob/master/example/example.go#L11) From 24407fbce02ada829d7412db6c2c8d20b785d2b3 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 19 Mar 2016 15:47:11 +0800 Subject: [PATCH 33/93] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 2c3abd0..8f119d1 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,3 @@ for more see Atom API Docs [API Docs](https://godoc.org/github.com/nareix/mp4) [Atom API Docs](https://godoc.org/github.com/nareix/mp4/atom) - - -## Atoms manipulation - From 279c0cbb5e445b350ff4fa51055b089fc520067b Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 19 Mar 2016 15:47:35 +0800 Subject: [PATCH 34/93] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f119d1..23c2910 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Provides mp4 reader/writer and mp4 atom manipulations functions. Open a mp4 file and read the first sample: -``` +```go file, _ := os.Open("test.mp4") demuxer := &mp4.Demuxer{R: file} demuxer.ReadHeader() @@ -12,7 +12,7 @@ pts, dts, isKeyFrame, data, err := demuxer.TrackH264.ReadSample() do some seeking: -``` +```go demuxer.TrackH264.SeekToTime(2.0) ``` @@ -24,7 +24,7 @@ learn more about mp4 atoms [here](https://developer.apple.com/library/mac/docume you can access atom structs via `Demuxer.TrackH264.TrackAtom`. for example: -``` +```go // Get the raw TimeScale field inside `mvhd` atom fmt.Println(demuxer.TrackH264.TrackAtom.Media.Header.TimeScale) ``` From 3294a57e488f1fab6f9559b6514f96971ac736ef Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 29 Mar 2016 22:34:29 +0800 Subject: [PATCH 35/93] add isom funcs WriteElemStreamDescAAC/ReadElemStreamDesc/ReadMPEG4AudioConfig/WriteMPEG4AudioConfig --- isom/isom.go | 422 ++++++++++++++++++++++++++++++++++++++++++++++ isom/isom_test.go | 42 +++++ 2 files changed, 464 insertions(+) create mode 100644 isom/isom.go create mode 100644 isom/isom_test.go diff --git a/isom/isom.go b/isom/isom.go new file mode 100644 index 0000000..351ace9 --- /dev/null +++ b/isom/isom.go @@ -0,0 +1,422 @@ +package isom + +import ( + "github.com/nareix/bits" + "io" + "bytes" + "fmt" + "io/ioutil" +) + +// copied from libavformat/isom.h +const ( + MP4ESDescrTag = 3 + MP4DecConfigDescrTag = 4 + MP4DecSpecificDescrTag = 5 +) + +// copied from libavcodec/mpeg4audio.h +const ( + AOT_AAC_MAIN = 1 + iota ///< Y Main + AOT_AAC_LC ///< Y Low Complexity + AOT_AAC_SSR ///< N (code in SoC repo) Scalable Sample Rate + AOT_AAC_LTP ///< Y Long Term Prediction + AOT_SBR ///< Y Spectral Band Replication + AOT_AAC_SCALABLE ///< N Scalable + AOT_TWINVQ ///< N Twin Vector Quantizer + AOT_CELP ///< N Code Excited Linear Prediction + AOT_HVXC ///< N Harmonic Vector eXcitation Coding + AOT_TTSI = 12 + iota ///< N Text-To-Speech Interface + AOT_MAINSYNTH ///< N Main Synthesis + AOT_WAVESYNTH ///< N Wavetable Synthesis + AOT_MIDI ///< N General MIDI + AOT_SAFX ///< N Algorithmic Synthesis and Audio Effects + AOT_ER_AAC_LC ///< N Error Resilient Low Complexity + AOT_ER_AAC_LTP = 19 + iota ///< N Error Resilient Long Term Prediction + AOT_ER_AAC_SCALABLE ///< N Error Resilient Scalable + AOT_ER_TWINVQ ///< N Error Resilient Twin Vector Quantizer + AOT_ER_BSAC ///< N Error Resilient Bit-Sliced Arithmetic Coding + AOT_ER_AAC_LD ///< N Error Resilient Low Delay + AOT_ER_CELP ///< N Error Resilient Code Excited Linear Prediction + AOT_ER_HVXC ///< N Error Resilient Harmonic Vector eXcitation Coding + AOT_ER_HILN ///< N Error Resilient Harmonic and Individual Lines plus Noise + AOT_ER_PARAM ///< N Error Resilient Parametric + AOT_SSC ///< N SinuSoidal Coding + AOT_PS ///< N Parametric Stereo + AOT_SURROUND ///< N MPEG Surround + AOT_ESCAPE ///< Y Escape Value + AOT_L1 ///< Y Layer 1 + AOT_L2 ///< Y Layer 2 + AOT_L3 ///< Y Layer 3 + AOT_DST ///< N Direct Stream Transfer + AOT_ALS ///< Y Audio LosslesS + AOT_SLS ///< N Scalable LosslesS + AOT_SLS_NON_CORE ///< N Scalable LosslesS (non core) + AOT_ER_AAC_ELD ///< N Error Resilient Enhanced Low Delay + AOT_SMR_SIMPLE ///< N Symbolic Music Representation Simple + AOT_SMR_MAIN ///< N Symbolic Music Representation Main + AOT_USAC_NOSBR ///< N Unified Speech and Audio Coding (no SBR) + AOT_SAOC ///< N Spatial Audio Object Coding + AOT_LD_SURROUND ///< N Low Delay MPEG Surround + AOT_USAC ///< N Unified Speech and Audio Coding +) + +type MPEG4AudioConfig struct { + SampleRate int + ChannelCount int + ObjectType uint + SampleRateIndex uint + ChannelConfig uint +} + +var sampleRateTable = []int{ + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000, 7350, +} + +var chanConfigTable = []int{ + 0, 1, 2, 3, 4, 5, 6, 8, +} + +func readObjectType(r *bits.Reader) (objectType uint, err error) { + if objectType, err = r.ReadBits(5); err != nil { + return + } + if objectType == AOT_ESCAPE { + var i uint + if i, err = r.ReadBits(6); err != nil { + return + } + objectType = 32 + i + } + return +} + +func writeObjectType(w *bits.Writer, objectType uint) (err error) { + if objectType >= 32 { + if err = w.WriteBits(AOT_ESCAPE, 5); err != nil { + return + } + if err = w.WriteBits(objectType-32, 6); err != nil { + return + } + } else { + if err = w.WriteBits(objectType, 5); err != nil { + return + } + } + return +} + +func readSampleRateIndex(r *bits.Reader) (index uint, err error) { + if index, err = r.ReadBits(4); err != nil { + return + } + if index == 0xf { + if index, err = r.ReadBits(24); err != nil { + return + } + } + return +} + +func writeSampleRateIndex(w *bits.Writer, index uint) (err error) { + if index >= 0xf { + if err = w.WriteBits(0xf, 4); err != nil { + return + } + if err = w.WriteBits(index, 24); err != nil { + return + } + } else { + if err = w.WriteBits(index, 4); err != nil { + return + } + } + return +} + +// copied from libavcodec/mpeg4audio.c avpriv_mpeg4audio_get_config() +func ReadMPEG4AudioConfig(r io.Reader) (config MPEG4AudioConfig, err error) { + br := &bits.Reader{R: r} + + if config.ObjectType, err = readObjectType(br); err != nil { + return + } + if config.SampleRateIndex, err = readSampleRateIndex(br); err != nil { + return + } + if int(config.SampleRateIndex) < len(sampleRateTable) { + config.SampleRate = sampleRateTable[config.SampleRateIndex] + } + if config.ChannelConfig, err = br.ReadBits(4); err != nil { + return + } + if int(config.ChannelConfig) < len(chanConfigTable) { + config.ChannelCount = chanConfigTable[config.ChannelConfig] + } + return +} + +func WriteMPEG4AudioConfig(w io.Writer, config MPEG4AudioConfig) (err error) { + bw := &bits.Writer{W: w} + + if err = writeObjectType(bw, config.ObjectType); err != nil { + return + } + + if config.SampleRateIndex == 0 { + for i, rate := range sampleRateTable { + if rate == config.SampleRate { + config.SampleRateIndex = uint(i) + } + } + } + if err = writeSampleRateIndex(bw, config.SampleRateIndex); err != nil { + return + } + + if config.ChannelConfig == 0 { + for i, count := range chanConfigTable { + if count == config.ChannelCount { + config.ChannelConfig = uint(i) + } + } + } + if err = bw.WriteBits(config.ChannelConfig, 4); err != nil { + return + } + + if err = bw.FlushBits(); err != nil { + return + } + return +} + +func readDesc(r io.Reader) (tag uint, data []byte, err error) { + if tag, err = bits.ReadUIntBE(r, 8); err != nil { + return + } + var length uint + for i := 0; i < 4; i++ { + var c uint + if c, err = bits.ReadUIntBE(r, 8); err != nil { + return + } + length = (length << 7) | (c & 0x7f) + if c&0x80 == 0 { + break + } + } + data = make([]byte, length) + if _, err = r.Read(data); err != nil { + return + } + return +} + +func writeDesc(w io.Writer, tag uint, data []byte) (err error) { + if err = bits.WriteUIntBE(w, tag, 8); err != nil { + return + } + length := uint(len(data)) + for length > 0 { + val := length&0x7f + if length >= 0x80 { + val |= 0x80 + } + if err = bits.WriteUIntBE(w, val, 8); err != nil { + return + } + length >>= 7 + } + if _, err = w.Write(data); err != nil { + return + } + return +} + +func readESDesc(r io.Reader) (err error) { + // ES_ID + if _, err = bits.ReadUIntBE(r, 16); err != nil { + return + } + var flags uint + if flags, err = bits.ReadUIntBE(r, 8); err != nil { + return + } + //streamDependenceFlag + if flags&0x80 != 0 { + if _, err = bits.ReadUIntBE(r, 16); err != nil { + return + } + } + //URL_Flag + if flags&0x40 != 0 { + var length uint + if length, err = bits.ReadUIntBE(r, 8); err != nil { + return + } + if _, err = io.CopyN(ioutil.Discard, r, int64(length)); err != nil { + return + } + } + //OCRstreamFlag + if flags&0x20 != 0 { + if _, err = bits.ReadUIntBE(r, 16); err != nil { + return + } + } + return +} + +func writeESDesc(w io.Writer) (err error) { + // ES_ID + if err = bits.WriteUIntBE(w, 0, 16); err != nil { + return + } + // flags + if err = bits.WriteUIntBE(w, 0, 8); err != nil { + return + } + return +} + +// copied from libavformat/isom.c ff_mp4_read_dec_config_descr() +func readDecConfDesc(r io.Reader) (decConfig []byte, err error) { + var objectId uint + var streamType uint + var bufSize uint + var maxBitrate uint + var avgBitrate uint + + // objectId + if objectId, err = bits.ReadUIntBE(r, 8); err != nil { + return + } + // streamType + if streamType, err = bits.ReadUIntBE(r, 8); err != nil { + return + } + // buffer size db + if bufSize, err = bits.ReadUIntBE(r, 24); err != nil { + return + } + // max bitrate + if maxBitrate, err = bits.ReadUIntBE(r, 32); err != nil { + return + } + // avg bitrate + if avgBitrate, err = bits.ReadUIntBE(r, 32); err != nil { + return + } + + if false { + println("readDecConfDesc", objectId, streamType, bufSize, maxBitrate, avgBitrate) + } + + var tag uint + var data []byte + if tag, data, err = readDesc(r); err != nil { + return + } + if tag != MP4DecSpecificDescrTag { + err = fmt.Errorf("MP4DecSpecificDescrTag not found") + return + } + decConfig = data + return +} + +// copied from libavformat/movenc.c mov_write_esds_tag() +func writeDecConfDesc(w io.Writer, objectId uint, streamType uint, decConfig []byte) (err error) { + // objectId + if err = bits.WriteUIntBE(w, objectId, 8); err != nil { + return + } + // streamType + if err = bits.WriteUIntBE(w, streamType, 8); err != nil { + return + } + // buffer size db + if err = bits.WriteUIntBE(w, 0, 24); err != nil { + return + } + // max bitrate + if err = bits.WriteUIntBE(w, 0, 32); err != nil { + return + } + // avg bitrate + if err = bits.WriteUIntBE(w, 0, 32); err != nil { + return + } + if err = writeDesc(w, MP4DecSpecificDescrTag, decConfig); err != nil { + return + } + return +} + +// copied from libavformat/mov.c ff_mov_read_esds() +func ReadElemStreamDesc(r io.Reader) (decConfig []byte, err error) { + var tag uint + var data []byte + if tag, data, err = readDesc(r); err != nil { + return + } + if tag != MP4ESDescrTag { + err = fmt.Errorf("MP4ESDescrTag not found") + return + } + r = bytes.NewReader(data) + + if err = readESDesc(r); err != nil { + return + } + if tag, data, err = readDesc(r); err != nil { + return + } + if tag != MP4DecConfigDescrTag { + err = fmt.Errorf("MP4DecSpecificDescrTag not found") + return + } + r = bytes.NewReader(data) + + if decConfig, err = readDecConfDesc(r); err != nil { + return + } + return +} + +func WriteElemStreamDescAAC(w io.Writer, config MPEG4AudioConfig) (err error) { + // MP4ESDescrTag(ESDesc MP4DecConfigDescrTag(objectId streamType bufSize avgBitrate MP4DecSpecificDescrTag(decConfig))) + + buf := &bytes.Buffer{} + WriteMPEG4AudioConfig(buf, config) + data := buf.Bytes() + + buf = &bytes.Buffer{} + // 0x40 = ObjectType AAC + // 0x15 = Audiostream + writeDecConfDesc(buf, 0x40, 0x15, data) + data = buf.Bytes() + + buf = &bytes.Buffer{} + writeDesc(buf, MP4DecConfigDescrTag, data) + data = buf.Bytes() + + buf = &bytes.Buffer{} + writeESDesc(buf) + buf.Write(data) + data = buf.Bytes() + + buf = &bytes.Buffer{} + writeDesc(buf, MP4ESDescrTag, data) + data = buf.Bytes() + + if _, err = w.Write(data); err != nil { + return + } + + return +} + diff --git a/isom/isom_test.go b/isom/isom_test.go new file mode 100644 index 0000000..3bd83e6 --- /dev/null +++ b/isom/isom_test.go @@ -0,0 +1,42 @@ + +package isom + +import ( + "testing" + "encoding/hex" + "bytes" +) + +func TestReadElemStreamDesc(t *testing.T) { + var decConfig []byte + var err error + + data, _ := hex.DecodeString("03808080220002000480808014401500000000030d400000000005808080021210068080800102") + t.Logf("length=%d", len(data)) + + if decConfig, err = ReadElemStreamDesc(bytes.NewReader(data)); err != nil { + t.Error(err) + } + t.Logf("decConfig=%x", decConfig) + + var aconfig MPEG4AudioConfig + if aconfig, err = ReadMPEG4AudioConfig(bytes.NewReader(decConfig)); err != nil { + t.Error(err) + } + t.Logf("aconfig=%v", aconfig) + + bw := &bytes.Buffer{} + WriteMPEG4AudioConfig(bw, aconfig) + t.Logf("decConfig=%x", bw.Bytes()) + + bw = &bytes.Buffer{} + WriteElemStreamDescAAC(bw, aconfig) + t.Logf("elemDesc=%x", bw.Bytes()) + data = bw.Bytes() + + if decConfig, err = ReadElemStreamDesc(bytes.NewReader(data)); err != nil { + t.Error(err) + } + t.Logf("decConfig=%x", decConfig) +} + From 02b80d86df38909423892558f5ef0b473f347b6b Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 29 Mar 2016 23:12:26 +0800 Subject: [PATCH 36/93] add ReadADTSHeader() --- isom/isom.go | 58 +++++++++++++++++++++++++++++++++++++++++------ isom/isom_test.go | 7 ++++++ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/isom/isom.go b/isom/isom.go index 351ace9..7650eaf 100644 --- a/isom/isom.go +++ b/isom/isom.go @@ -1,10 +1,10 @@ package isom import ( - "github.com/nareix/bits" - "io" "bytes" "fmt" + "github.com/nareix/bits" + "io" "io/ioutil" ) @@ -62,11 +62,11 @@ const ( ) type MPEG4AudioConfig struct { - SampleRate int - ChannelCount int + SampleRate int + ChannelCount int ObjectType uint SampleRateIndex uint - ChannelConfig uint + ChannelConfig uint } var sampleRateTable = []int{ @@ -78,6 +78,51 @@ var chanConfigTable = []int{ 0, 1, 2, 3, 4, 5, 6, 8, } +func ReadADTSHeader(data []byte) (objectType, sampleRateIndex, chanConfig, frameLength uint) { + br := &bits.Reader{R: bytes.NewReader(data)} + + //Structure + //AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ) + //Header consists of 7 or 9 bytes (without or with CRC). + + //A 12 syncword 0xFFF, all bits must be 1 + br.ReadBits(12) + //B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2 + br.ReadBits(1) + //C 2 Layer: always 0 + br.ReadBits(2) + //D 1 protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC + br.ReadBits(1) + + //E 2 profile, the MPEG-4 Audio Object Type minus 1 + objectType, _ = br.ReadBits(2) + objectType++ + //F 4 MPEG-4 Sampling Frequency Index (15 is forbidden) + sampleRateIndex, _ = br.ReadBits(4) + //G 1 private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding + br.ReadBits(1) + //H 3 MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE) + chanConfig, _ = br.ReadBits(3) + //I 1 originality, set to 0 when encoding, ignore when decoding + br.ReadBits(1) + //J 1 home, set to 0 when encoding, ignore when decoding + br.ReadBits(1) + //K 1 copyrighted id bit, the next bit of a centrally registered copyright identifier, set to 0 when encoding, ignore when decoding + br.ReadBits(1) + //L 1 copyright id start, signals that this frame's copyright id bit is the first bit of the copyright id, set to 0 when encoding, ignore when decoding + br.ReadBits(1) + + //M 13 frame length, this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame) + frameLength, _ = br.ReadBits(13) + //O 11 Buffer fullness + br.ReadBits(11) + //P 2 Number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame + br.ReadBits(2) + + //Q 16 CRC if protection absent is 0 + return +} + func readObjectType(r *bits.Reader) (objectType uint, err error) { if objectType, err = r.ReadBits(5); err != nil { return @@ -221,7 +266,7 @@ func writeDesc(w io.Writer, tag uint, data []byte) (err error) { } length := uint(len(data)) for length > 0 { - val := length&0x7f + val := length & 0x7f if length >= 0x80 { val |= 0x80 } @@ -419,4 +464,3 @@ func WriteElemStreamDescAAC(w io.Writer, config MPEG4AudioConfig) (err error) { return } - diff --git a/isom/isom_test.go b/isom/isom_test.go index 3bd83e6..00722fe 100644 --- a/isom/isom_test.go +++ b/isom/isom_test.go @@ -38,5 +38,12 @@ func TestReadElemStreamDesc(t *testing.T) { t.Error(err) } t.Logf("decConfig=%x", decConfig) + + //00000000 ff f1 50 80 04 3f fc de 04 00 00 6c 69 62 66 61 |..P..?.....libfa| + //00000010 61 63 20 31 2e 32 38 00 00 42 40 93 20 04 32 00 |ac 1.28..B@. .2.| + //00000020 47 ff f1 50 80 05 1f fc 21 42 fe ed b2 5c a8 00 |G..P....!B...\..| + data, _ = hex.DecodeString("fff15080043ffcde040000") + objectType, sampleRateIndex, chanConfig, frameLength := ReadADTSHeader(data) + t.Logf("objectType=%d sampleRateIndex=%d chanConfig=%d frameLength=%d", objectType, sampleRateIndex, chanConfig, frameLength) } From f1628505e7e3812b1b4bbe98f0c403c2c55b8c50 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 31 Mar 2016 00:34:08 +0800 Subject: [PATCH 37/93] add MPEG4AudioConfig Complete() func --- isom/isom.go | 28 ++++++++++++++++++++++------ isom/isom_test.go | 7 +++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/isom/isom.go b/isom/isom.go index 7650eaf..951330f 100644 --- a/isom/isom.go +++ b/isom/isom.go @@ -181,6 +181,17 @@ func writeSampleRateIndex(w *bits.Writer, index uint) (err error) { return } +func (self MPEG4AudioConfig) Complete() (config MPEG4AudioConfig) { + config = self + if int(config.SampleRateIndex) < len(sampleRateTable) { + config.SampleRate = sampleRateTable[config.SampleRateIndex] + } + if int(config.ChannelConfig) < len(chanConfigTable) { + config.ChannelCount = chanConfigTable[config.ChannelConfig] + } + return +} + // copied from libavcodec/mpeg4audio.c avpriv_mpeg4audio_get_config() func ReadMPEG4AudioConfig(r io.Reader) (config MPEG4AudioConfig, err error) { br := &bits.Reader{R: r} @@ -191,15 +202,9 @@ func ReadMPEG4AudioConfig(r io.Reader) (config MPEG4AudioConfig, err error) { if config.SampleRateIndex, err = readSampleRateIndex(br); err != nil { return } - if int(config.SampleRateIndex) < len(sampleRateTable) { - config.SampleRate = sampleRateTable[config.SampleRateIndex] - } if config.ChannelConfig, err = br.ReadBits(4); err != nil { return } - if int(config.ChannelConfig) < len(chanConfigTable) { - config.ChannelCount = chanConfigTable[config.ChannelConfig] - } return } @@ -432,6 +437,17 @@ func ReadElemStreamDesc(r io.Reader) (decConfig []byte, err error) { return } +func ReadElemStreamDescAAC(r io.Reader) (config MPEG4AudioConfig, err error) { + var data []byte + if data, err = ReadElemStreamDesc(r); err != nil { + return + } + if config, err = ReadMPEG4AudioConfig(bytes.NewReader(data)); err != nil { + return + } + return +} + func WriteElemStreamDescAAC(w io.Writer, config MPEG4AudioConfig) (err error) { // MP4ESDescrTag(ESDesc MP4DecConfigDescrTag(objectId streamType bufSize avgBitrate MP4DecSpecificDescrTag(decConfig))) diff --git a/isom/isom_test.go b/isom/isom_test.go index 00722fe..13a7558 100644 --- a/isom/isom_test.go +++ b/isom/isom_test.go @@ -1,10 +1,9 @@ - package isom import ( - "testing" - "encoding/hex" "bytes" + "encoding/hex" + "testing" ) func TestReadElemStreamDesc(t *testing.T) { @@ -23,6 +22,7 @@ func TestReadElemStreamDesc(t *testing.T) { if aconfig, err = ReadMPEG4AudioConfig(bytes.NewReader(decConfig)); err != nil { t.Error(err) } + aconfig = aconfig.Complete() t.Logf("aconfig=%v", aconfig) bw := &bytes.Buffer{} @@ -46,4 +46,3 @@ func TestReadElemStreamDesc(t *testing.T) { objectType, sampleRateIndex, chanConfig, frameLength := ReadADTSHeader(data) t.Logf("objectType=%d sampleRateIndex=%d chanConfig=%d frameLength=%d", objectType, sampleRateIndex, chanConfig, frameLength) } - From c241ae20c47f5650f1001fa465d77b1f9a7694fe Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 31 Mar 2016 00:34:58 +0800 Subject: [PATCH 38/93] rename demuxer func names --- demuxer.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/demuxer.go b/demuxer.go index 5741009..36d06d2 100644 --- a/demuxer.go +++ b/demuxer.go @@ -3,6 +3,8 @@ package mp4 import ( "github.com/nareix/mp4/atom" + "github.com/nareix/mp4/isom" + "bytes" "fmt" "io" ) @@ -65,19 +67,22 @@ func (self *Demuxer) ReadHeader() (err error) { return } if record := atom.GetAVCDecoderConfRecordByTrack(atrack); record != nil { - track.Type = H264 - self.TrackH264 = track if len(record.PPS) > 0 { - track.PPS = record.PPS[0] + track.pps = record.PPS[0] } if len(record.SPS) > 0 { - track.SPS = record.SPS[0] + track.sps = record.SPS[0] } + track.Type = H264 + self.TrackH264 = track self.Tracks = append(self.Tracks, track) - } else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil { - self.TrackAAC = track - track.Type = AAC - self.Tracks = append(self.Tracks, track) + } else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil && mp4a.Conf != nil { + if config, err := isom.ReadElemStreamDescAAC(bytes.NewReader(mp4a.Conf.Data)); err == nil { + track.mpeg4AudioConfig = config.Complete() + track.Type = AAC + self.TrackAAC = track + self.Tracks = append(self.Tracks, track) + } } } @@ -384,7 +389,15 @@ func (self *Track) TimeStampToTime(ts int64) float64 { return float64(ts)/float64(self.TrackAtom.Media.Header.TimeScale) } -func (self *Track) WriteSample(pts int64, dts int64, data []byte) (err error) { - return +func (self *Track) TimeScale() int64 { + return int64(self.TrackAtom.Media.Header.TimeScale) +} + +func (self *Track) GetH264PPSAndSPS() (pps, sps []byte) { + return self.pps, self.sps +} + +func (self *Track) GetMPEG4AudioConfig() isom.MPEG4AudioConfig { + return self.mpeg4AudioConfig } From 21f37b23a4d9e16b88f1e6f4de967fd933c3e3da Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 31 Mar 2016 00:35:32 +0800 Subject: [PATCH 39/93] add Track fields --- track.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/track.go b/track.go index 87f40f8..7b56c6c 100644 --- a/track.go +++ b/track.go @@ -3,6 +3,7 @@ package mp4 import ( "github.com/nareix/mp4/atom" + "github.com/nareix/mp4/isom" "io" ) @@ -13,11 +14,14 @@ const ( type Track struct { Type int - SPS []byte - PPS []byte TrackAtom *atom.Track r io.ReadSeeker + sps []byte + pps []byte + + mpeg4AudioConfig isom.MPEG4AudioConfig + sample *atom.SampleTable sampleIndex int @@ -34,5 +38,11 @@ type Track struct { chunkGroupIndex int chunkIndex int sampleIndexInChunk int + + sampleToChunkEntry *atom.SampleToChunkEntry + sttsEntry *atom.TimeToSampleEntry + cttsEntry *atom.CompositionOffsetEntry + writeMdat func ([]byte) error + lastDts int64 } From 387f02a9ac7f0e7c9005aad464e851e4104f3930 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 31 Mar 2016 00:35:44 +0800 Subject: [PATCH 40/93] add AddAACTrack --- muxer.go | 321 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 311 insertions(+), 10 deletions(-) diff --git a/muxer.go b/muxer.go index 89f8702..b4a083c 100644 --- a/muxer.go +++ b/muxer.go @@ -3,16 +3,316 @@ package mp4 import ( "github.com/nareix/mp4/atom" + "github.com/nareix/mp4/isom" "io" + "bytes" "fmt" ) +type Muxer struct { + W io.WriteSeeker + Tracks []*Track + TrackH264 *Track + TrackAAC *Track + + mdatWriter *atom.Writer +} + +func (self *Muxer) AddAACTrack() (track *Track) { + track = &Track{} + track.Type = AAC + + track.sample = &atom.SampleTable{ + SampleDesc: &atom.SampleDesc{ + Mp4aDesc: &atom.Mp4aDesc{ + DataRefIdx: 1, + NumberOfChannels: 0, // fill later + SampleSize: 0, // fill later + SampleRate: 0, // fill later + Conf: &atom.ElemStreamDesc{}, + }, + }, + TimeToSample: &atom.TimeToSample{}, + SampleToChunk: &atom.SampleToChunk{ + Entries: []atom.SampleToChunkEntry{ + { + FirstChunk: 1, + SampleDescId: 1, + }, + }, + }, + SampleSize: &atom.SampleSize{}, + ChunkOffset: &atom.ChunkOffset{ + Entries: []int{8}, + }, + } + track.sampleToChunkEntry = &track.sample.SampleToChunk.Entries[0] + track.writeMdat = self.writeMdat + + track.TrackAtom = &atom.Track{ + Header: &atom.TrackHeader{ + TrackId: len(self.Tracks)+1, + Flags: 0x0003, // Track enabled | Track in movie + Duration: 0, // fill later + Volume: atom.IntToFixed(1), + AlternateGroup: 1, + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + }, + Media: &atom.Media{ + Header: &atom.MediaHeader{ + TimeScale: 0, // fill later + Duration: 0, // fill later + }, + Info: &atom.MediaInfo{ + Sound: &atom.SoundMediaInfo{ + }, + Sample: track.sample, + Data: &atom.DataInfo{ + Refer: &atom.DataRefer{ + Url: &atom.DataReferUrl{ + Flags: 0x000001, // Self reference + }, + }, + }, + }, + Handler: &atom.HandlerRefer{ + SubType: "soun", + Name: "Sound Handler", + }, + }, + } + + self.TrackAAC = track + self.Tracks = append(self.Tracks, track) + return +} + +func (self *Muxer) AddH264Track() (track *Track) { + track = &Track{} + track.Type = H264 + + track.sample = &atom.SampleTable{ + SampleDesc: &atom.SampleDesc{ + Avc1Desc: &atom.Avc1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: 0, // fill later + Height: 0, // fill later + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + Conf: &atom.Avc1Conf{}, + }, + }, + TimeToSample: &atom.TimeToSample{}, + SampleToChunk: &atom.SampleToChunk{ + Entries: []atom.SampleToChunkEntry{ + { + FirstChunk: 1, + SampleDescId: 1, + }, + }, + }, + SampleSize: &atom.SampleSize{}, + ChunkOffset: &atom.ChunkOffset{ + Entries: []int{8}, + }, + SyncSample: &atom.SyncSample{}, + } + track.sampleToChunkEntry = &track.sample.SampleToChunk.Entries[0] + track.writeMdat = self.writeMdat + + track.TrackAtom = &atom.Track{ + Header: &atom.TrackHeader{ + TrackId: len(self.Tracks)+1, + Flags: 0x0003, // Track enabled | Track in movie + Duration: 0, // fill later + Volume: atom.IntToFixed(1), + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + TrackWidth: atom.IntToFixed(0), // fill later + TrackHeight: atom.IntToFixed(0), // fill later + }, + + Media: &atom.Media{ + Header: &atom.MediaHeader{ + TimeScale: 0, // fill later + Duration: 0, // fill later + }, + Info: &atom.MediaInfo{ + Video: &atom.VideoMediaInfo{ + Flags: 0x000001, + }, + Sample: track.sample, + Data: &atom.DataInfo{ + Refer: &atom.DataRefer{ + Url: &atom.DataReferUrl{ + Flags: 0x000001, // Self reference + }, + }, + }, + }, + Handler: &atom.HandlerRefer{ + SubType: "vide", + Name: "Video Media Handler", + }, + }, + } + + self.TrackH264 = track + self.Tracks = append(self.Tracks, track) + return +} + +func (self *Muxer) writeMdat(data []byte) (err error) { + _, err = self.mdatWriter.Write(data) + return +} + +func (self *Muxer) WriteHeader() (err error) { + if self.mdatWriter, err = atom.WriteAtomHeader(self.W, "mdat"); err != nil { + return + } + return +} + +func (self *Track) SetH264PPSAndSPS(pps, sps []byte) { + self.pps, self.sps = pps, sps +} + +func (self *Track) SetMPEG4AudioConfig(config isom.MPEG4AudioConfig) { + self.mpeg4AudioConfig = config +} + +func (self *Track) SetTimeScale(timeScale int64) { + self.TrackAtom.Media.Header.TimeScale = int(timeScale) + return +} + +func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { + sampleSize := len(data) + if err = self.writeMdat(data); err != nil { + return + } + + if isKeyFrame && self.sample.SyncSample != nil { + self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, self.sampleIndex+1) + } + + if self.sampleIndex > 0 { + if dts <= self.lastDts { + err = fmt.Errorf("dts must be incremental") + return + } + duration := int(dts-self.lastDts) + if self.sttsEntry == nil || duration != self.sttsEntry.Duration { + self.sttsEntry = &atom.TimeToSampleEntry{Duration: duration} + self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, *self.sttsEntry) + } + self.sttsEntry.Count++ + } + + if pts != dts { + if pts < dts { + err = fmt.Errorf("pts must greater than dts") + return + } + offset := int(pts-dts) + if self.cttsEntry == nil || offset != self.cttsEntry.Offset { + self.cttsEntry = &atom.CompositionOffsetEntry{Offset: offset} + if self.sample.CompositionOffset == nil { + self.sample.CompositionOffset = &atom.CompositionOffset{} + } + self.sample.CompositionOffset.Entries = append(self.sample.CompositionOffset.Entries, *self.cttsEntry) + } + self.cttsEntry.Count++ + } + + self.lastDts = dts + self.sampleIndex++ + self.sampleToChunkEntry.SamplesPerChunk++ + self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, sampleSize) + + return +} + +func (self *Track) fillTrackAtom() (err error) { + if self.Type == H264 { + self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( + self.sps, + self.pps, + ) + if err != nil { + return + } + var info *atom.H264SPSInfo + if info, err = atom.ParseH264SPS(self.sps[1:]); err != nil { + return + } + self.sample.SampleDesc.Avc1Desc.Width = int(info.Width) + self.sample.SampleDesc.Avc1Desc.Height = int(info.Height) + self.TrackAtom.Header.Duration = int(self.lastDts) + self.TrackAtom.Header.TrackWidth = atom.IntToFixed(int(info.Width)) + self.TrackAtom.Header.TrackHeight = atom.IntToFixed(int(info.Height)) + self.TrackAtom.Media.Header.Duration = int(self.lastDts) + } else if self.Type == AAC { + buf := &bytes.Buffer{} + config := self.mpeg4AudioConfig.Complete() + if err = isom.WriteElemStreamDescAAC(buf, config); err != nil { + return + } + self.sample.SampleDesc.Mp4aDesc.Conf.Data = buf.Bytes() + self.sample.SampleDesc.Mp4aDesc.NumberOfChannels = config.ChannelCount + self.sample.SampleDesc.Mp4aDesc.SampleSize = config.ChannelCount*8 + self.sample.SampleDesc.Mp4aDesc.SampleRate = atom.IntToFixed(config.SampleRate) + self.TrackAtom.Header.Duration = int(self.lastDts) + self.TrackAtom.Media.Header.Duration = int(self.lastDts) + } + return +} + +func (self *Muxer) WriteTrailer() (err error) { + moov := &atom.Movie{} + moov.Header = &atom.MovieHeader{ + PreferredRate: atom.IntToFixed(1), + PreferredVolume: atom.IntToFixed(1), + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + NextTrackId: 2, + } + timeScale := 0 + duration := 0 + for _, track := range(self.Tracks) { + if err = track.fillTrackAtom(); err != nil { + return + } + if track.TrackAtom.Media.Header.TimeScale > timeScale { + timeScale = track.TrackAtom.Media.Header.TimeScale + } + if track.TrackAtom.Media.Header.Duration > duration { + duration = track.TrackAtom.Media.Header.Duration + } + moov.Tracks = append(moov.Tracks, track.TrackAtom) + } + moov.Header.TimeScale = timeScale + moov.Header.Duration = duration + + if err = self.mdatWriter.Close(); err != nil { + return + } + if err = atom.WriteMovie(self.W, moov); err != nil { + return + } + + return +} + +/* type SimpleH264Writer struct { W io.WriteSeeker TimeScale int - SPS []byte - PPS []byte + sps []byte + pps []byte Width int Height int @@ -32,19 +332,19 @@ func (self *SimpleH264Writer) prepare() (err error) { return } - if len(self.SPS) == 0 { - err = fmt.Errorf("invalid SPS") + if len(self.sps) == 0 { + err = fmt.Errorf("invalid sps") return } - if len(self.PPS) == 0 { - err = fmt.Errorf("invalid PPS") + if len(self.pps) == 0 { + err = fmt.Errorf("invalid pps") return } if self.Width == 0 || self.Height == 0 { - var info *atom.H264SPSInfo - if info, err = atom.ParseH264SPS(self.SPS[1:]); err != nil { + var info *atom.H264spsInfo + if info, err = atom.ParseH264sps(self.sps[1:]); err != nil { return } self.Width = int(info.Width) @@ -155,8 +455,8 @@ func (self *SimpleH264Writer) writeSample(isNALU, sync bool, duration int, data func (self *SimpleH264Writer) Finish() (err error) { self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( - self.SPS, - self.PPS, + self.sps, + self.pps, ) if err != nil { return @@ -226,4 +526,5 @@ func (self *SimpleH264Writer) Finish() (err error) { return } +*/ From 37f64f9a63925fca8ca1546167528bd888ffe026 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 31 Mar 2016 14:34:16 +0800 Subject: [PATCH 41/93] fix sttsEntry count bug --- muxer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/muxer.go b/muxer.go index b4a083c..f79ae9f 100644 --- a/muxer.go +++ b/muxer.go @@ -206,8 +206,8 @@ func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byt } duration := int(dts-self.lastDts) if self.sttsEntry == nil || duration != self.sttsEntry.Duration { - self.sttsEntry = &atom.TimeToSampleEntry{Duration: duration} - self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, *self.sttsEntry) + self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, atom.TimeToSampleEntry{Duration: duration}) + self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1] } self.sttsEntry.Count++ } @@ -258,7 +258,7 @@ func (self *Track) fillTrackAtom() (err error) { } else if self.Type == AAC { buf := &bytes.Buffer{} config := self.mpeg4AudioConfig.Complete() - if err = isom.WriteElemStreamDescAAC(buf, config); err != nil { + if err = isom.WriteElemStreamDescAAC(buf, config, uint(self.TrackAtom.Header.TrackId)); err != nil { return } self.sample.SampleDesc.Mp4aDesc.Conf.Data = buf.Bytes() From 7e9a708556403106a1c1ebe9a6101c2018073bfb Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 16:17:10 +0800 Subject: [PATCH 42/93] add newTrack() --- muxer.go | 138 +++++++++++++++++++------------------------------------ 1 file changed, 46 insertions(+), 92 deletions(-) diff --git a/muxer.go b/muxer.go index f79ae9f..3036721 100644 --- a/muxer.go +++ b/muxer.go @@ -18,20 +18,11 @@ type Muxer struct { mdatWriter *atom.Writer } -func (self *Muxer) AddAACTrack() (track *Track) { - track = &Track{} - track.Type = AAC +func (self *Muxer) newTrack() *Track { + track := &Track{} track.sample = &atom.SampleTable{ - SampleDesc: &atom.SampleDesc{ - Mp4aDesc: &atom.Mp4aDesc{ - DataRefIdx: 1, - NumberOfChannels: 0, // fill later - SampleSize: 0, // fill later - SampleRate: 0, // fill later - Conf: &atom.ElemStreamDesc{}, - }, - }, + SampleDesc: &atom.SampleDesc{}, TimeToSample: &atom.TimeToSample{}, SampleToChunk: &atom.SampleToChunk{ Entries: []atom.SampleToChunkEntry{ @@ -46,8 +37,6 @@ func (self *Muxer) AddAACTrack() (track *Track) { Entries: []int{8}, }, } - track.sampleToChunkEntry = &track.sample.SampleToChunk.Entries[0] - track.writeMdat = self.writeMdat track.TrackAtom = &atom.Track{ Header: &atom.TrackHeader{ @@ -75,96 +64,60 @@ func (self *Muxer) AddAACTrack() (track *Track) { }, }, }, - Handler: &atom.HandlerRefer{ - SubType: "soun", - Name: "Sound Handler", - }, }, } - self.TrackAAC = track + track.writeMdat = self.writeMdat + track.sampleToChunkEntry = &track.sample.SampleToChunk.Entries[0] self.Tracks = append(self.Tracks, track) + + return track +} + +func (self *Muxer) AddAACTrack() (track *Track) { + track = self.newTrack() + self.TrackAAC = track + track.Type = AAC + track.sample.SampleDesc.Mp4aDesc = &atom.Mp4aDesc{ + DataRefIdx: 1, + NumberOfChannels: 0, // fill later + SampleSize: 0, // fill later + SampleRate: 0, // fill later + Conf: &atom.ElemStreamDesc{}, + } + track.TrackAtom.Media.Handler = &atom.HandlerRefer{ + SubType: "soun", + Name: "Sound Handler", + } return } func (self *Muxer) AddH264Track() (track *Track) { - track = &Track{} + track = self.newTrack() track.Type = H264 - - track.sample = &atom.SampleTable{ - SampleDesc: &atom.SampleDesc{ - Avc1Desc: &atom.Avc1Desc{ - DataRefIdx: 1, - HorizontalResolution: 72, - VorizontalResolution: 72, - Width: 0, // fill later - Height: 0, // fill later - FrameCount: 1, - Depth: 24, - ColorTableId: -1, - Conf: &atom.Avc1Conf{}, - }, - }, - TimeToSample: &atom.TimeToSample{}, - SampleToChunk: &atom.SampleToChunk{ - Entries: []atom.SampleToChunkEntry{ - { - FirstChunk: 1, - SampleDescId: 1, - }, - }, - }, - SampleSize: &atom.SampleSize{}, - ChunkOffset: &atom.ChunkOffset{ - Entries: []int{8}, - }, - SyncSample: &atom.SyncSample{}, - } - track.sampleToChunkEntry = &track.sample.SampleToChunk.Entries[0] - track.writeMdat = self.writeMdat - - track.TrackAtom = &atom.Track{ - Header: &atom.TrackHeader{ - TrackId: len(self.Tracks)+1, - Flags: 0x0003, // Track enabled | Track in movie - Duration: 0, // fill later - Volume: atom.IntToFixed(1), - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - TrackWidth: atom.IntToFixed(0), // fill later - TrackHeight: atom.IntToFixed(0), // fill later - }, - - Media: &atom.Media{ - Header: &atom.MediaHeader{ - TimeScale: 0, // fill later - Duration: 0, // fill later - }, - Info: &atom.MediaInfo{ - Video: &atom.VideoMediaInfo{ - Flags: 0x000001, - }, - Sample: track.sample, - Data: &atom.DataInfo{ - Refer: &atom.DataRefer{ - Url: &atom.DataReferUrl{ - Flags: 0x000001, // Self reference - }, - }, - }, - }, - Handler: &atom.HandlerRefer{ - SubType: "vide", - Name: "Video Media Handler", - }, - }, - } - self.TrackH264 = track - self.Tracks = append(self.Tracks, track) + track.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: 0, // fill later + Height: 0, // fill later + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + Conf: &atom.Avc1Conf{}, + } + track.TrackAtom.Media.Handler = &atom.HandlerRefer{ + SubType: "vide", + Name: "Video Media Handler", + } return } -func (self *Muxer) writeMdat(data []byte) (err error) { +func (self *Muxer) writeMdat(data []byte) (pos int64, err error) { + if pos, err = self.mdatWriter.Seek(0, 1); err != nil { + return + } _, err = self.mdatWriter.Write(data) return } @@ -190,8 +143,9 @@ func (self *Track) SetTimeScale(timeScale int64) { } func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { + //var filePos int64 sampleSize := len(data) - if err = self.writeMdat(data); err != nil { + if _, err = self.writeMdat(data); err != nil { return } From 08a51950781076c301b5c8145acb4dab542abc09 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 16:25:00 +0800 Subject: [PATCH 43/93] fix chunkOffset bug --- muxer.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/muxer.go b/muxer.go index 3036721..1459534 100644 --- a/muxer.go +++ b/muxer.go @@ -29,13 +29,12 @@ func (self *Muxer) newTrack() *Track { { FirstChunk: 1, SampleDescId: 1, + SamplesPerChunk: 1, }, }, }, SampleSize: &atom.SampleSize{}, - ChunkOffset: &atom.ChunkOffset{ - Entries: []int{8}, - }, + ChunkOffset: &atom.ChunkOffset{}, } track.TrackAtom = &atom.Track{ @@ -68,7 +67,6 @@ func (self *Muxer) newTrack() *Track { } track.writeMdat = self.writeMdat - track.sampleToChunkEntry = &track.sample.SampleToChunk.Entries[0] self.Tracks = append(self.Tracks, track) return track @@ -94,8 +92,8 @@ func (self *Muxer) AddAACTrack() (track *Track) { func (self *Muxer) AddH264Track() (track *Track) { track = self.newTrack() - track.Type = H264 self.TrackH264 = track + track.Type = H264 track.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ DataRefIdx: 1, HorizontalResolution: 72, @@ -143,9 +141,9 @@ func (self *Track) SetTimeScale(timeScale int64) { } func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { - //var filePos int64 + var filePos int64 sampleSize := len(data) - if _, err = self.writeMdat(data); err != nil { + if filePos, err = self.writeMdat(data); err != nil { return } @@ -184,7 +182,7 @@ func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byt self.lastDts = dts self.sampleIndex++ - self.sampleToChunkEntry.SamplesPerChunk++ + self.sample.ChunkOffset.Entries = append(self.sample.ChunkOffset.Entries, int(filePos)) self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, sampleSize) return From 767dcd85b6ba9fd7bee93d8fc7bbbd6c29d7cf85 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 16:47:07 +0800 Subject: [PATCH 44/93] fix missing track.TrackAtom.Media.Info.Sound and cttsEntry append pointer bug --- muxer.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/muxer.go b/muxer.go index 1459534..289e066 100644 --- a/muxer.go +++ b/muxer.go @@ -42,8 +42,6 @@ func (self *Muxer) newTrack() *Track { TrackId: len(self.Tracks)+1, Flags: 0x0003, // Track enabled | Track in movie Duration: 0, // fill later - Volume: atom.IntToFixed(1), - AlternateGroup: 1, Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, }, Media: &atom.Media{ @@ -52,8 +50,6 @@ func (self *Muxer) newTrack() *Track { Duration: 0, // fill later }, Info: &atom.MediaInfo{ - Sound: &atom.SoundMediaInfo{ - }, Sample: track.sample, Data: &atom.DataInfo{ Refer: &atom.DataRefer{ @@ -83,10 +79,13 @@ func (self *Muxer) AddAACTrack() (track *Track) { SampleRate: 0, // fill later Conf: &atom.ElemStreamDesc{}, } + track.TrackAtom.Header.Volume = atom.IntToFixed(1) + track.TrackAtom.Header.AlternateGroup = 1 track.TrackAtom.Media.Handler = &atom.HandlerRefer{ SubType: "soun", Name: "Sound Handler", } + track.TrackAtom.Media.Info.Sound = &atom.SoundMediaInfo{} return } @@ -109,6 +108,10 @@ func (self *Muxer) AddH264Track() (track *Track) { SubType: "vide", Name: "Video Media Handler", } + track.sample.CompositionOffset = &atom.CompositionOffset{} + track.TrackAtom.Media.Info.Video = &atom.VideoMediaInfo{ + Flags: 0x000001, + } return } @@ -164,18 +167,16 @@ func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byt self.sttsEntry.Count++ } - if pts != dts { + if self.sample.CompositionOffset != nil { if pts < dts { err = fmt.Errorf("pts must greater than dts") return } offset := int(pts-dts) if self.cttsEntry == nil || offset != self.cttsEntry.Offset { - self.cttsEntry = &atom.CompositionOffsetEntry{Offset: offset} - if self.sample.CompositionOffset == nil { - self.sample.CompositionOffset = &atom.CompositionOffset{} - } - self.sample.CompositionOffset.Entries = append(self.sample.CompositionOffset.Entries, *self.cttsEntry) + table := self.sample.CompositionOffset + table.Entries = append(table.Entries, atom.CompositionOffsetEntry{Offset: offset}) + self.cttsEntry = &table.Entries[len(table.Entries)-1] } self.cttsEntry.Count++ } From 84e73c5011bb015f539eb2667044f94d8331a0f8 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 16:50:12 +0800 Subject: [PATCH 45/93] make dump pretty --- atom/dumper.go | 67 ++++++----- atom/genStruct.js | 21 ++-- atom/struct.go | 277 +++++++++++++++++++++++++--------------------- 3 files changed, 203 insertions(+), 162 deletions(-) diff --git a/atom/dumper.go b/atom/dumper.go index 2403036..c18f911 100644 --- a/atom/dumper.go +++ b/atom/dumper.go @@ -2,68 +2,85 @@ package atom import ( + "io" "fmt" "strings" "encoding/hex" ) type Walker interface { - Start() - Log(string) + FilterArrayItem(string,string,int,int) bool + ArrayLeft(int,int) + StartStruct(string) + EndStruct() Name(string) Int(int) Fixed(Fixed) String(string) Bytes([]byte) TimeStamp(TimeStamp) - End() } type Dumper struct { + W io.Writer depth int -} - -func (self *Dumper) Start() { - self.depth++ -} - -func (self *Dumper) End() { - self.depth-- + name string + arrlen int + arridx int } func (self Dumper) tab() string { return strings.Repeat(" ", self.depth*2) } -func (self Dumper) Name(name string) { - fmt.Print(self.tab(), name, ": ") +func (self Dumper) println(msg string) { + fmt.Fprintln(self.W, self.tab()+msg) } -func (self Dumper) Log(msg string) { - fmt.Println(self.tab()+msg) +func (self *Dumper) ArrayLeft(i int, n int) { + self.println(fmt.Sprintf("... total %d elements", n)) } -func (self Dumper) logVal(msg string) { - fmt.Println(msg) +func (self *Dumper) FilterArrayItem(name string, field string, i int, n int) bool { + if n > 20 && i > 20 { + return false + } + return true +} + +func (self *Dumper) EndArray() { +} + +func (self *Dumper) StartStruct(name string) { + self.depth++ + self.println(fmt.Sprintf("[%s]", name)) +} + +func (self *Dumper) EndStruct() { + self.depth-- +} + +func (self *Dumper) Name(name string) { + self.name = name } func (self Dumper) Int(val int) { - self.logVal(fmt.Sprintf("%d", val)) -} - -func (self Dumper) Fixed(val Fixed) { - self.logVal(fmt.Sprintf("%d", FixedToInt(val))) + self.println(fmt.Sprintf("%s: %d", self.name, val)) } func (self Dumper) String(val string) { - self.logVal(val) + self.println(fmt.Sprintf("%s: %s", self.name, val)) +} + +func (self Dumper) Fixed(val Fixed) { + self.println(fmt.Sprintf("%s: %d", self.name, FixedToInt(val))) } func (self Dumper) Bytes(val []byte) { - self.logVal(hex.EncodeToString(val)) + self.println(fmt.Sprintf("%s: %s", self.name, hex.EncodeToString(val))) } func (self Dumper) TimeStamp(val TimeStamp) { - self.logVal(fmt.Sprintf("%d", int(val))) + self.println(fmt.Sprintf("%s: %d", self.name, int(val))) } diff --git a/atom/genStruct.js b/atom/genStruct.js index b7efad8..4c1c2c7 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -93,8 +93,8 @@ var atoms = { fields: [ ['$atoms', [ ['header', '*mediaHeader'], - ['info', '*mediaInfo'], ['handler', '*handlerRefer'], + ['info', '*mediaInfo'], ]], ], }, @@ -526,9 +526,15 @@ var DeclDumpFunc = (opts) => { }; var dumpArr = (name, type, id) => { - return For(`_, item := range(${name})`, [ - dumpCommonType('item', type, id), - ]); + return [ + //Call('w.StartArray', [`"${id}"`, `len(${name})`]), + For(`i, item := range(${name})`, If( + `w.FilterArrayItem("${opts.type}", "${id}", i, len(${name}))`, + dumpCommonType('item', type, id), + [`w.ArrayLeft(i, len(${name}))`, 'break'] + )), + //Call('w.EndArray', []), + ]; }; var dumpCommonType = (name, type, id) => { @@ -554,12 +560,9 @@ var DeclDumpFunc = (opts) => { }; var dumpFields = fields => - [ - Call('w.Log', [`"[${opts.type}]"`]), - Call('w.Start', []), - ] + [ Call('w.StartStruct', [`"${opts.type}"`]) ] .concat(fields.map(field => dumpField(field.name, field.type))) - .concat([Call('w.End', [])]); + .concat([Call('w.EndStruct', [])]); return Func( 'Walk'+opts.type, diff --git a/atom/struct.go b/atom/struct.go index 85689c6..17128b5 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -81,20 +81,24 @@ func WriteMovie(w io.WriteSeeker, self *Movie) (err error) { } func WalkMovie(w Walker, self *Movie) { - w.Log("[Movie]") - w.Start() + w.StartStruct("Movie") if self.Header != nil { WalkMovieHeader(w, self.Header) } if self.Iods != nil { WalkIods(w, self.Iods) } - for _, item := range self.Tracks { - if item != nil { - WalkTrack(w, item) + for i, item := range self.Tracks { + if w.FilterArrayItem("Movie", "Tracks", i, len(self.Tracks)) { + if item != nil { + WalkTrack(w, item) + } + } else { + w.ArrayLeft(i, len(self.Tracks)) + break } } - w.End() + w.EndStruct() return } @@ -128,11 +132,10 @@ func WriteIods(w io.WriteSeeker, self *Iods) (err error) { } func WalkIods(w Walker, self *Iods) { - w.Log("[Iods]") - w.Start() + w.StartStruct("Iods") w.Name("Data") w.Bytes(self.Data) - w.End() + w.EndStruct() return } @@ -281,8 +284,7 @@ func WriteMovieHeader(w io.WriteSeeker, self *MovieHeader) (err error) { } func WalkMovieHeader(w Walker, self *MovieHeader) { - w.Log("[MovieHeader]") - w.Start() + w.StartStruct("MovieHeader") w.Name("Version") w.Int(self.Version) w.Name("Flags") @@ -299,9 +301,14 @@ func WalkMovieHeader(w Walker, self *MovieHeader) { w.Fixed(self.PreferredRate) w.Name("PreferredVolume") w.Fixed(self.PreferredVolume) - for _, item := range self.Matrix { - w.Name("Matrix") - w.Int(item) + for i, item := range self.Matrix { + if w.FilterArrayItem("MovieHeader", "Matrix", i, len(self.Matrix)) { + w.Name("Matrix") + w.Int(item) + } else { + w.ArrayLeft(i, len(self.Matrix)) + break + } } w.Name("PreviewTime") w.TimeStamp(self.PreviewTime) @@ -317,7 +324,7 @@ func WalkMovieHeader(w Walker, self *MovieHeader) { w.TimeStamp(self.CurrentTime) w.Name("NextTrackId") w.Int(self.NextTrackId) - w.End() + w.EndStruct() return } @@ -381,15 +388,14 @@ func WriteTrack(w io.WriteSeeker, self *Track) (err error) { } func WalkTrack(w Walker, self *Track) { - w.Log("[Track]") - w.Start() + w.StartStruct("Track") if self.Header != nil { WalkTrackHeader(w, self.Header) } if self.Media != nil { WalkMedia(w, self.Media) } - w.End() + w.EndStruct() return } @@ -522,8 +528,7 @@ func WriteTrackHeader(w io.WriteSeeker, self *TrackHeader) (err error) { } func WalkTrackHeader(w Walker, self *TrackHeader) { - w.Log("[TrackHeader]") - w.Start() + w.StartStruct("TrackHeader") w.Name("Version") w.Int(self.Version) w.Name("Flags") @@ -542,15 +547,20 @@ func WalkTrackHeader(w Walker, self *TrackHeader) { w.Int(self.AlternateGroup) w.Name("Volume") w.Fixed(self.Volume) - for _, item := range self.Matrix { - w.Name("Matrix") - w.Int(item) + for i, item := range self.Matrix { + if w.FilterArrayItem("TrackHeader", "Matrix", i, len(self.Matrix)) { + w.Name("Matrix") + w.Int(item) + } else { + w.ArrayLeft(i, len(self.Matrix)) + break + } } w.Name("TrackWidth") w.Fixed(self.TrackWidth) w.Name("TrackHeight") w.Fixed(self.TrackHeight) - w.End() + w.EndStruct() return } @@ -612,8 +622,7 @@ func WriteHandlerRefer(w io.WriteSeeker, self *HandlerRefer) (err error) { } func WalkHandlerRefer(w Walker, self *HandlerRefer) { - w.Log("[HandlerRefer]") - w.Start() + w.StartStruct("HandlerRefer") w.Name("Version") w.Int(self.Version) w.Name("Flags") @@ -624,14 +633,14 @@ func WalkHandlerRefer(w Walker, self *HandlerRefer) { w.String(self.SubType) w.Name("Name") w.String(self.Name) - w.End() + w.EndStruct() return } type Media struct { Header *MediaHeader - Info *MediaInfo Handler *HandlerRefer + Info *MediaInfo } func ReadMedia(r *io.LimitedReader) (res *Media, err error) { @@ -650,18 +659,18 @@ func ReadMedia(r *io.LimitedReader) (res *Media, err error) { return } } - case "minf": - { - if self.Info, err = ReadMediaInfo(ar); err != nil { - return - } - } case "hdlr": { if self.Handler, err = ReadHandlerRefer(ar); err != nil { return } } + case "minf": + { + if self.Info, err = ReadMediaInfo(ar); err != nil { + return + } + } } if _, err = ReadDummy(ar, int(ar.N)); err != nil { @@ -683,13 +692,13 @@ func WriteMedia(w io.WriteSeeker, self *Media) (err error) { return } } - if self.Info != nil { - if err = WriteMediaInfo(w, self.Info); err != nil { + if self.Handler != nil { + if err = WriteHandlerRefer(w, self.Handler); err != nil { return } } - if self.Handler != nil { - if err = WriteHandlerRefer(w, self.Handler); err != nil { + if self.Info != nil { + if err = WriteMediaInfo(w, self.Info); err != nil { return } } @@ -700,18 +709,17 @@ func WriteMedia(w io.WriteSeeker, self *Media) (err error) { } func WalkMedia(w Walker, self *Media) { - w.Log("[Media]") - w.Start() + w.StartStruct("Media") if self.Header != nil { WalkMediaHeader(w, self.Header) } - if self.Info != nil { - WalkMediaInfo(w, self.Info) - } if self.Handler != nil { WalkHandlerRefer(w, self.Handler) } - w.End() + if self.Info != nil { + WalkMediaInfo(w, self.Info) + } + w.EndStruct() return } @@ -794,8 +802,7 @@ func WriteMediaHeader(w io.WriteSeeker, self *MediaHeader) (err error) { } func WalkMediaHeader(w Walker, self *MediaHeader) { - w.Log("[MediaHeader]") - w.Start() + w.StartStruct("MediaHeader") w.Name("Version") w.Int(self.Version) w.Name("Flags") @@ -812,7 +819,7 @@ func WalkMediaHeader(w Walker, self *MediaHeader) { w.Int(self.Language) w.Name("Quality") w.Int(self.Quality) - w.End() + w.EndStruct() return } @@ -900,8 +907,7 @@ func WriteMediaInfo(w io.WriteSeeker, self *MediaInfo) (err error) { } func WalkMediaInfo(w Walker, self *MediaInfo) { - w.Log("[MediaInfo]") - w.Start() + w.StartStruct("MediaInfo") if self.Sound != nil { WalkSoundMediaInfo(w, self.Sound) } @@ -914,7 +920,7 @@ func WalkMediaInfo(w Walker, self *MediaInfo) { if self.Sample != nil { WalkSampleTable(w, self.Sample) } - w.End() + w.EndStruct() return } @@ -966,12 +972,11 @@ func WriteDataInfo(w io.WriteSeeker, self *DataInfo) (err error) { } func WalkDataInfo(w Walker, self *DataInfo) { - w.Log("[DataInfo]") - w.Start() + w.StartStruct("DataInfo") if self.Refer != nil { WalkDataRefer(w, self.Refer) } - w.End() + w.EndStruct() return } @@ -1049,8 +1054,7 @@ func WriteDataRefer(w io.WriteSeeker, self *DataRefer) (err error) { } func WalkDataRefer(w Walker, self *DataRefer) { - w.Log("[DataRefer]") - w.Start() + w.StartStruct("DataRefer") w.Name("Version") w.Int(self.Version) w.Name("Flags") @@ -1058,7 +1062,7 @@ func WalkDataRefer(w Walker, self *DataRefer) { if self.Url != nil { WalkDataReferUrl(w, self.Url) } - w.End() + w.EndStruct() return } @@ -1099,13 +1103,12 @@ func WriteDataReferUrl(w io.WriteSeeker, self *DataReferUrl) (err error) { } func WalkDataReferUrl(w Walker, self *DataReferUrl) { - w.Log("[DataReferUrl]") - w.Start() + w.StartStruct("DataReferUrl") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) - w.End() + w.EndStruct() return } @@ -1159,15 +1162,14 @@ func WriteSoundMediaInfo(w io.WriteSeeker, self *SoundMediaInfo) (err error) { } func WalkSoundMediaInfo(w Walker, self *SoundMediaInfo) { - w.Log("[SoundMediaInfo]") - w.Start() + w.StartStruct("SoundMediaInfo") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) w.Name("Balance") w.Int(self.Balance) - w.End() + w.EndStruct() return } @@ -1226,19 +1228,23 @@ func WriteVideoMediaInfo(w io.WriteSeeker, self *VideoMediaInfo) (err error) { } func WalkVideoMediaInfo(w Walker, self *VideoMediaInfo) { - w.Log("[VideoMediaInfo]") - w.Start() + w.StartStruct("VideoMediaInfo") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) w.Name("GraphicsMode") w.Int(self.GraphicsMode) - for _, item := range self.Opcolor { - w.Name("Opcolor") - w.Int(item) + for i, item := range self.Opcolor { + if w.FilterArrayItem("VideoMediaInfo", "Opcolor", i, len(self.Opcolor)) { + w.Name("Opcolor") + w.Int(item) + } else { + w.ArrayLeft(i, len(self.Opcolor)) + break + } } - w.End() + w.EndStruct() return } @@ -1362,8 +1368,7 @@ func WriteSampleTable(w io.WriteSeeker, self *SampleTable) (err error) { } func WalkSampleTable(w Walker, self *SampleTable) { - w.Log("[SampleTable]") - w.Start() + w.StartStruct("SampleTable") if self.SampleDesc != nil { WalkSampleDesc(w, self.SampleDesc) } @@ -1385,7 +1390,7 @@ func WalkSampleTable(w Walker, self *SampleTable) { if self.SampleSize != nil { WalkSampleSize(w, self.SampleSize) } - w.End() + w.EndStruct() return } @@ -1475,8 +1480,7 @@ func WriteSampleDesc(w io.WriteSeeker, self *SampleDesc) (err error) { } func WalkSampleDesc(w Walker, self *SampleDesc) { - w.Log("[SampleDesc]") - w.Start() + w.StartStruct("SampleDesc") w.Name("Version") w.Int(self.Version) if self.Avc1Desc != nil { @@ -1485,7 +1489,7 @@ func WalkSampleDesc(w Walker, self *SampleDesc) { if self.Mp4aDesc != nil { WalkMp4aDesc(w, self.Mp4aDesc) } - w.End() + w.EndStruct() return } @@ -1605,8 +1609,7 @@ func WriteMp4aDesc(w io.WriteSeeker, self *Mp4aDesc) (err error) { } func WalkMp4aDesc(w Walker, self *Mp4aDesc) { - w.Log("[Mp4aDesc]") - w.Start() + w.StartStruct("Mp4aDesc") w.Name("DataRefIdx") w.Int(self.DataRefIdx) w.Name("Version") @@ -1626,7 +1629,7 @@ func WalkMp4aDesc(w Walker, self *Mp4aDesc) { if self.Conf != nil { WalkElemStreamDesc(w, self.Conf) } - w.End() + w.EndStruct() return } @@ -1667,13 +1670,12 @@ func WriteElemStreamDesc(w io.WriteSeeker, self *ElemStreamDesc) (err error) { } func WalkElemStreamDesc(w Walker, self *ElemStreamDesc) { - w.Log("[ElemStreamDesc]") - w.Start() + w.StartStruct("ElemStreamDesc") w.Name("Version") w.Int(self.Version) w.Name("Data") w.Bytes(self.Data) - w.End() + w.EndStruct() return } @@ -1835,8 +1837,7 @@ func WriteAvc1Desc(w io.WriteSeeker, self *Avc1Desc) (err error) { } func WalkAvc1Desc(w Walker, self *Avc1Desc) { - w.Log("[Avc1Desc]") - w.Start() + w.StartStruct("Avc1Desc") w.Name("DataRefIdx") w.Int(self.DataRefIdx) w.Name("Version") @@ -1868,7 +1869,7 @@ func WalkAvc1Desc(w Walker, self *Avc1Desc) { if self.Conf != nil { WalkAvc1Conf(w, self.Conf) } - w.End() + w.EndStruct() return } @@ -1902,10 +1903,9 @@ func WriteAvc1Conf(w io.WriteSeeker, self *Avc1Conf) (err error) { } func WalkAvc1Conf(w Walker, self *Avc1Conf) { - w.Log("[Avc1Conf]") - w.Start() + w.StartStruct("Avc1Conf") WalkAVCDecoderConfRecord(w, self.Record) - w.End() + w.EndStruct() return } @@ -1965,16 +1965,20 @@ func WriteTimeToSample(w io.WriteSeeker, self *TimeToSample) (err error) { } func WalkTimeToSample(w Walker, self *TimeToSample) { - w.Log("[TimeToSample]") - w.Start() + w.StartStruct("TimeToSample") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) - for _, item := range self.Entries { - WalkTimeToSampleEntry(w, item) + for i, item := range self.Entries { + if w.FilterArrayItem("TimeToSample", "Entries", i, len(self.Entries)) { + WalkTimeToSampleEntry(w, item) + } else { + w.ArrayLeft(i, len(self.Entries)) + break + } } - w.End() + w.EndStruct() return } @@ -2005,13 +2009,12 @@ func WriteTimeToSampleEntry(w io.WriteSeeker, self TimeToSampleEntry) (err error } func WalkTimeToSampleEntry(w Walker, self TimeToSampleEntry) { - w.Log("[TimeToSampleEntry]") - w.Start() + w.StartStruct("TimeToSampleEntry") w.Name("Count") w.Int(self.Count) w.Name("Duration") w.Int(self.Duration) - w.End() + w.EndStruct() return } @@ -2071,16 +2074,20 @@ func WriteSampleToChunk(w io.WriteSeeker, self *SampleToChunk) (err error) { } func WalkSampleToChunk(w Walker, self *SampleToChunk) { - w.Log("[SampleToChunk]") - w.Start() + w.StartStruct("SampleToChunk") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) - for _, item := range self.Entries { - WalkSampleToChunkEntry(w, item) + for i, item := range self.Entries { + if w.FilterArrayItem("SampleToChunk", "Entries", i, len(self.Entries)) { + WalkSampleToChunkEntry(w, item) + } else { + w.ArrayLeft(i, len(self.Entries)) + break + } } - w.End() + w.EndStruct() return } @@ -2118,15 +2125,14 @@ func WriteSampleToChunkEntry(w io.WriteSeeker, self SampleToChunkEntry) (err err } func WalkSampleToChunkEntry(w Walker, self SampleToChunkEntry) { - w.Log("[SampleToChunkEntry]") - w.Start() + w.StartStruct("SampleToChunkEntry") w.Name("FirstChunk") w.Int(self.FirstChunk) w.Name("SamplesPerChunk") w.Int(self.SamplesPerChunk) w.Name("SampleDescId") w.Int(self.SampleDescId) - w.End() + w.EndStruct() return } @@ -2186,16 +2192,20 @@ func WriteCompositionOffset(w io.WriteSeeker, self *CompositionOffset) (err erro } func WalkCompositionOffset(w Walker, self *CompositionOffset) { - w.Log("[CompositionOffset]") - w.Start() + w.StartStruct("CompositionOffset") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) - for _, item := range self.Entries { - WalkCompositionOffsetEntry(w, item) + for i, item := range self.Entries { + if w.FilterArrayItem("CompositionOffset", "Entries", i, len(self.Entries)) { + WalkCompositionOffsetEntry(w, item) + } else { + w.ArrayLeft(i, len(self.Entries)) + break + } } - w.End() + w.EndStruct() return } @@ -2226,13 +2236,12 @@ func WriteCompositionOffsetEntry(w io.WriteSeeker, self CompositionOffsetEntry) } func WalkCompositionOffsetEntry(w Walker, self CompositionOffsetEntry) { - w.Log("[CompositionOffsetEntry]") - w.Start() + w.StartStruct("CompositionOffsetEntry") w.Name("Count") w.Int(self.Count) w.Name("Offset") w.Int(self.Offset) - w.End() + w.EndStruct() return } @@ -2292,17 +2301,21 @@ func WriteSyncSample(w io.WriteSeeker, self *SyncSample) (err error) { } func WalkSyncSample(w Walker, self *SyncSample) { - w.Log("[SyncSample]") - w.Start() + w.StartStruct("SyncSample") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) - for _, item := range self.Entries { - w.Name("Entries") - w.Int(item) + for i, item := range self.Entries { + if w.FilterArrayItem("SyncSample", "Entries", i, len(self.Entries)) { + w.Name("Entries") + w.Int(item) + } else { + w.ArrayLeft(i, len(self.Entries)) + break + } } - w.End() + w.EndStruct() return } @@ -2369,19 +2382,23 @@ func WriteSampleSize(w io.WriteSeeker, self *SampleSize) (err error) { } func WalkSampleSize(w Walker, self *SampleSize) { - w.Log("[SampleSize]") - w.Start() + w.StartStruct("SampleSize") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) w.Name("SampleSize") w.Int(self.SampleSize) - for _, item := range self.Entries { - w.Name("Entries") - w.Int(item) + for i, item := range self.Entries { + if w.FilterArrayItem("SampleSize", "Entries", i, len(self.Entries)) { + w.Name("Entries") + w.Int(item) + } else { + w.ArrayLeft(i, len(self.Entries)) + break + } } - w.End() + w.EndStruct() return } @@ -2441,16 +2458,20 @@ func WriteChunkOffset(w io.WriteSeeker, self *ChunkOffset) (err error) { } func WalkChunkOffset(w Walker, self *ChunkOffset) { - w.Log("[ChunkOffset]") - w.Start() + w.StartStruct("ChunkOffset") w.Name("Version") w.Int(self.Version) w.Name("Flags") w.Int(self.Flags) - for _, item := range self.Entries { - w.Name("Entries") - w.Int(item) + for i, item := range self.Entries { + if w.FilterArrayItem("ChunkOffset", "Entries", i, len(self.Entries)) { + w.Name("Entries") + w.Int(item) + } else { + w.ArrayLeft(i, len(self.Entries)) + break + } } - w.End() + w.EndStruct() return } From a405187d475035489eb1d45e5d0f4a2f070aa4e8 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 16:50:53 +0800 Subject: [PATCH 46/93] add filePos for writeMdat, remove Track unsed field --- track.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/track.go b/track.go index 7b56c6c..0d13ef2 100644 --- a/track.go +++ b/track.go @@ -39,10 +39,9 @@ type Track struct { chunkIndex int sampleIndexInChunk int - sampleToChunkEntry *atom.SampleToChunkEntry sttsEntry *atom.TimeToSampleEntry cttsEntry *atom.CompositionOffsetEntry - writeMdat func ([]byte) error + writeMdat func ([]byte) (int64,error) lastDts int64 } From 7d11ff6303941bdbe607746b0d5ba489c105f943 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 17:30:35 +0800 Subject: [PATCH 47/93] fix stts entry sample count bug --- muxer.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/muxer.go b/muxer.go index 289e066..3619e22 100644 --- a/muxer.go +++ b/muxer.go @@ -48,6 +48,7 @@ func (self *Muxer) newTrack() *Track { Header: &atom.MediaHeader{ TimeScale: 0, // fill later Duration: 0, // fill later + Language: 21956, }, Info: &atom.MediaInfo{ Sample: track.sample, @@ -190,6 +191,9 @@ func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byt } func (self *Track) fillTrackAtom() (err error) { + if self.sampleIndex > 0 { + self.sttsEntry.Count++ + } if self.Type == H264 { self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( self.sps, From 941cd2a168f22df8acd54e3eea4fb1f5b62b448c Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 18:00:49 +0800 Subject: [PATCH 48/93] add SyncSample for h264 track --- muxer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/muxer.go b/muxer.go index 3619e22..73ea9a4 100644 --- a/muxer.go +++ b/muxer.go @@ -105,6 +105,7 @@ func (self *Muxer) AddH264Track() (track *Track) { ColorTableId: -1, Conf: &atom.Avc1Conf{}, } + track.sample.SyncSample = &atom.SyncSample{} track.TrackAtom.Media.Handler = &atom.HandlerRefer{ SubType: "vide", Name: "Video Media Handler", From f00dbdeb2429f80b20fb2590812601bafea59ff1 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 18:18:32 +0800 Subject: [PATCH 49/93] fix moov duration calc bug --- muxer.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/muxer.go b/muxer.go index 73ea9a4..ca37dbc 100644 --- a/muxer.go +++ b/muxer.go @@ -209,7 +209,6 @@ func (self *Track) fillTrackAtom() (err error) { } self.sample.SampleDesc.Avc1Desc.Width = int(info.Width) self.sample.SampleDesc.Avc1Desc.Height = int(info.Height) - self.TrackAtom.Header.Duration = int(self.lastDts) self.TrackAtom.Header.TrackWidth = atom.IntToFixed(int(info.Width)) self.TrackAtom.Header.TrackHeight = atom.IntToFixed(int(info.Height)) self.TrackAtom.Media.Header.Duration = int(self.lastDts) @@ -223,7 +222,6 @@ func (self *Track) fillTrackAtom() (err error) { self.sample.SampleDesc.Mp4aDesc.NumberOfChannels = config.ChannelCount self.sample.SampleDesc.Mp4aDesc.SampleSize = config.ChannelCount*8 self.sample.SampleDesc.Mp4aDesc.SampleRate = atom.IntToFixed(config.SampleRate) - self.TrackAtom.Header.Duration = int(self.lastDts) self.TrackAtom.Media.Header.Duration = int(self.lastDts) } return @@ -237,22 +235,22 @@ func (self *Muxer) WriteTrailer() (err error) { Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, NextTrackId: 2, } - timeScale := 0 - duration := 0 + + maxDur := float64(0) + timeScale := 10000 for _, track := range(self.Tracks) { if err = track.fillTrackAtom(); err != nil { return } - if track.TrackAtom.Media.Header.TimeScale > timeScale { - timeScale = track.TrackAtom.Media.Header.TimeScale - } - if track.TrackAtom.Media.Header.Duration > duration { - duration = track.TrackAtom.Media.Header.Duration + dur := track.Duration() + track.TrackAtom.Header.Duration = int(float64(timeScale)*dur) + if dur > maxDur { + maxDur = dur } moov.Tracks = append(moov.Tracks, track.TrackAtom) } moov.Header.TimeScale = timeScale - moov.Header.Duration = duration + moov.Header.Duration = int(float64(timeScale)*maxDur) if err = self.mdatWriter.Close(); err != nil { return From 662146e1dcb1f7be4552c1ed63cafdb12a52416e Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 18:19:08 +0800 Subject: [PATCH 50/93] make esds tag calc result same as libav --- isom/isom.go | 97 +++++++++++++++++++++++++++++------------------ isom/isom_test.go | 20 +++++----- 2 files changed, 69 insertions(+), 48 deletions(-) diff --git a/isom/isom.go b/isom/isom.go index 951330f..959ed17 100644 --- a/isom/isom.go +++ b/isom/isom.go @@ -15,6 +15,9 @@ const ( MP4DecSpecificDescrTag = 5 ) +var debugReader = false +var debugWriter = false + // copied from libavcodec/mpeg4audio.h const ( AOT_AAC_MAIN = 1 + iota ///< Y Main @@ -270,15 +273,13 @@ func writeDesc(w io.Writer, tag uint, data []byte) (err error) { return } length := uint(len(data)) - for length > 0 { - val := length & 0x7f - if length >= 0x80 { - val |= 0x80 - } - if err = bits.WriteUIntBE(w, val, 8); err != nil { + for i := 3; i > 0; i-- { + if err = bits.WriteUIntBE(w, (length>>uint(7*i))&0x7f|0x80, 8); err != nil { return } - length >>= 7 + } + if err = bits.WriteUIntBE(w, length&0x7f, 8); err != nil { + return } if _, err = w.Write(data); err != nil { return @@ -287,8 +288,9 @@ func writeDesc(w io.Writer, tag uint, data []byte) (err error) { } func readESDesc(r io.Reader) (err error) { + var ES_ID uint // ES_ID - if _, err = bits.ReadUIntBE(r, 16); err != nil { + if ES_ID, err = bits.ReadUIntBE(r, 16); err != nil { return } var flags uint @@ -317,12 +319,15 @@ func readESDesc(r io.Reader) (err error) { return } } + if debugReader { + println("readESDesc:", ES_ID, flags) + } return } -func writeESDesc(w io.Writer) (err error) { +func writeESDesc(w io.Writer, ES_ID uint) (err error) { // ES_ID - if err = bits.WriteUIntBE(w, 0, 16); err != nil { + if err = bits.WriteUIntBE(w, ES_ID, 16); err != nil { return } // flags @@ -332,6 +337,28 @@ func writeESDesc(w io.Writer) (err error) { return } +func readDescByTag(r io.Reader, targetTag uint) (data []byte, err error) { + var found bool + for { + if tag, _data, err := readDesc(r); err != nil { + break + } else { + if tag == targetTag { + data = _data + found = true + } + if debugReader { + println("readDescByTag:", tag, len(_data)) + } + } + } + if !found { + err = fmt.Errorf("tag not found") + return + } + return +} + // copied from libavformat/isom.c ff_mp4_read_dec_config_descr() func readDecConfDesc(r io.Reader) (decConfig []byte, err error) { var objectId uint @@ -361,20 +388,13 @@ func readDecConfDesc(r io.Reader) (decConfig []byte, err error) { return } - if false { - println("readDecConfDesc", objectId, streamType, bufSize, maxBitrate, avgBitrate) + if debugReader { + println("readDecConfDesc:", objectId, streamType, bufSize, maxBitrate, avgBitrate) } - var tag uint - var data []byte - if tag, data, err = readDesc(r); err != nil { + if decConfig, err = readDescByTag(r, MP4DecSpecificDescrTag); err != nil { return } - if tag != MP4DecSpecificDescrTag { - err = fmt.Errorf("MP4DecSpecificDescrTag not found") - return - } - decConfig = data return } @@ -393,7 +413,7 @@ func writeDecConfDesc(w io.Writer, objectId uint, streamType uint, decConfig []b return } // max bitrate - if err = bits.WriteUIntBE(w, 0, 32); err != nil { + if err = bits.WriteUIntBE(w, 200000, 32); err != nil { return } // avg bitrate @@ -408,13 +428,12 @@ func writeDecConfDesc(w io.Writer, objectId uint, streamType uint, decConfig []b // copied from libavformat/mov.c ff_mov_read_esds() func ReadElemStreamDesc(r io.Reader) (decConfig []byte, err error) { - var tag uint - var data []byte - if tag, data, err = readDesc(r); err != nil { - return + if debugReader { + println("ReadElemStreamDesc: start") } - if tag != MP4ESDescrTag { - err = fmt.Errorf("MP4ESDescrTag not found") + + var data []byte + if data, err = readDescByTag(r, MP4ESDescrTag); err != nil { return } r = bytes.NewReader(data) @@ -422,11 +441,8 @@ func ReadElemStreamDesc(r io.Reader) (decConfig []byte, err error) { if err = readESDesc(r); err != nil { return } - if tag, data, err = readDesc(r); err != nil { - return - } - if tag != MP4DecConfigDescrTag { - err = fmt.Errorf("MP4DecSpecificDescrTag not found") + + if data, err = readDescByTag(r, MP4DecConfigDescrTag); err != nil { return } r = bytes.NewReader(data) @@ -434,6 +450,10 @@ func ReadElemStreamDesc(r io.Reader) (decConfig []byte, err error) { if decConfig, err = readDecConfDesc(r); err != nil { return } + + if debugReader { + println("ReadElemStreamDesc: end") + } return } @@ -442,13 +462,16 @@ func ReadElemStreamDescAAC(r io.Reader) (config MPEG4AudioConfig, err error) { if data, err = ReadElemStreamDesc(r); err != nil { return } + if debugReader { + println("decConfig: ", len(data)) + } if config, err = ReadMPEG4AudioConfig(bytes.NewReader(data)); err != nil { return } return } -func WriteElemStreamDescAAC(w io.Writer, config MPEG4AudioConfig) (err error) { +func WriteElemStreamDescAAC(w io.Writer, config MPEG4AudioConfig, trackId uint) (err error) { // MP4ESDescrTag(ESDesc MP4DecConfigDescrTag(objectId streamType bufSize avgBitrate MP4DecSpecificDescrTag(decConfig))) buf := &bytes.Buffer{} @@ -462,21 +485,21 @@ func WriteElemStreamDescAAC(w io.Writer, config MPEG4AudioConfig) (err error) { data = buf.Bytes() buf = &bytes.Buffer{} - writeDesc(buf, MP4DecConfigDescrTag, data) + writeDesc(buf, MP4DecConfigDescrTag, data) // 4 data = buf.Bytes() buf = &bytes.Buffer{} - writeESDesc(buf) + writeESDesc(buf, trackId) buf.Write(data) + writeDesc(buf, 0x06, []byte{0x02}) data = buf.Bytes() buf = &bytes.Buffer{} - writeDesc(buf, MP4ESDescrTag, data) + writeDesc(buf, MP4ESDescrTag, data) // 3 data = buf.Bytes() if _, err = w.Write(data); err != nil { return } - return } diff --git a/isom/isom_test.go b/isom/isom_test.go index 13a7558..6d81dfb 100644 --- a/isom/isom_test.go +++ b/isom/isom_test.go @@ -7,19 +7,17 @@ import ( ) func TestReadElemStreamDesc(t *testing.T) { - var decConfig []byte + debugReader = true + debugWriter = true + var err error data, _ := hex.DecodeString("03808080220002000480808014401500000000030d400000000005808080021210068080800102") + t.Logf("elemDesc=%x", data) t.Logf("length=%d", len(data)) - if decConfig, err = ReadElemStreamDesc(bytes.NewReader(data)); err != nil { - t.Error(err) - } - t.Logf("decConfig=%x", decConfig) - var aconfig MPEG4AudioConfig - if aconfig, err = ReadMPEG4AudioConfig(bytes.NewReader(decConfig)); err != nil { + if aconfig, err = ReadElemStreamDescAAC(bytes.NewReader(data)); err != nil { t.Error(err) } aconfig = aconfig.Complete() @@ -27,17 +25,17 @@ func TestReadElemStreamDesc(t *testing.T) { bw := &bytes.Buffer{} WriteMPEG4AudioConfig(bw, aconfig) - t.Logf("decConfig=%x", bw.Bytes()) bw = &bytes.Buffer{} - WriteElemStreamDescAAC(bw, aconfig) + WriteElemStreamDescAAC(bw, aconfig, 2) t.Logf("elemDesc=%x", bw.Bytes()) data = bw.Bytes() + t.Logf("length=%d", len(data)) - if decConfig, err = ReadElemStreamDesc(bytes.NewReader(data)); err != nil { + if aconfig, err = ReadElemStreamDescAAC(bytes.NewReader(data)); err != nil { t.Error(err) } - t.Logf("decConfig=%x", decConfig) + t.Logf("aconfig=%x", aconfig.Complete()) //00000000 ff f1 50 80 04 3f fc de 04 00 00 6c 69 62 66 61 |..P..?.....libfa| //00000010 61 63 20 31 2e 32 38 00 00 42 40 93 20 04 32 00 |ac 1.28..B@. .2.| From bdfae5551c8e98d0125d28002d0f14a44e0fae76 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Apr 2016 18:45:56 +0800 Subject: [PATCH 51/93] add ADTS support --- muxer.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/muxer.go b/muxer.go index ca37dbc..e96adef 100644 --- a/muxer.go +++ b/muxer.go @@ -146,6 +146,24 @@ func (self *Track) SetTimeScale(timeScale int64) { } func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { + + // check ADTSHeader(starts with FFF) + if len(data) > 7 && data[0]==0xff&&data[1]&0xf0==0xf0 { + if !self.mpeg4AudioConfig.IsValid() { + self.mpeg4AudioConfig, _ = isom.ReadADTSHeader(data) + } + // Skip ADTSHeader + if data[1]&0x1 == 0 { + if len(data) < 9 { + err = fmt.Errorf("ADTSHeader short read") + return + } + data = data[9:] + } else { + data = data[7:] + } + } + var filePos int64 sampleSize := len(data) if filePos, err = self.writeMdat(data); err != nil { @@ -195,6 +213,7 @@ func (self *Track) fillTrackAtom() (err error) { if self.sampleIndex > 0 { self.sttsEntry.Count++ } + if self.Type == H264 { self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( self.sps, @@ -213,6 +232,10 @@ func (self *Track) fillTrackAtom() (err error) { self.TrackAtom.Header.TrackHeight = atom.IntToFixed(int(info.Height)) self.TrackAtom.Media.Header.Duration = int(self.lastDts) } else if self.Type == AAC { + if !self.mpeg4AudioConfig.IsValid() { + err = fmt.Errorf("invalie MPEG4AudioConfig") + return + } buf := &bytes.Buffer{} config := self.mpeg4AudioConfig.Complete() if err = isom.WriteElemStreamDescAAC(buf, config, uint(self.TrackAtom.Header.TrackId)); err != nil { From 235dbc9b7b7d195d080a35eb5a8bebddac9703a1 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 2 Apr 2016 18:38:55 +0800 Subject: [PATCH 52/93] change func name --- demuxer.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/demuxer.go b/demuxer.go index 36d06d2..7d1b6df 100644 --- a/demuxer.go +++ b/demuxer.go @@ -321,10 +321,10 @@ func (self *Track) Duration() float64 { } func (self *Track) CurTime() float64 { - return self.TimeStampToTime(self.dts) + return self.TsToTime(self.dts) } -func (self *Track) CurTimeStamp() int64 { +func (self *Track) CurTs() int64 { return self.dts } @@ -342,7 +342,7 @@ func (self *Track) SeekToSampleIndex(index int) error { } func (self *Track) TimeToSampleIndex(time float64) int { - targetTs := self.TimeToTimeStamp(time) + targetTs := self.TimeToTs(time) targetIndex := 0 startTs := int64(0) @@ -381,11 +381,11 @@ func (self *Track) TimeToSampleIndex(time float64) int { return targetIndex } -func (self *Track) TimeToTimeStamp(time float64) int64 { +func (self *Track) TimeToTs(time float64) int64 { return int64(time*float64(self.TrackAtom.Media.Header.TimeScale)) } -func (self *Track) TimeStampToTime(ts int64) float64 { +func (self *Track) TsToTime(ts int64) float64 { return float64(ts)/float64(self.TrackAtom.Media.Header.TimeScale) } From 068f2d21af8964c37f1349251ea2a88858336735 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 2 Apr 2016 18:39:09 +0800 Subject: [PATCH 53/93] remove adts header detect --- muxer.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/muxer.go b/muxer.go index e96adef..e664f7e 100644 --- a/muxer.go +++ b/muxer.go @@ -146,24 +146,6 @@ func (self *Track) SetTimeScale(timeScale int64) { } func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { - - // check ADTSHeader(starts with FFF) - if len(data) > 7 && data[0]==0xff&&data[1]&0xf0==0xf0 { - if !self.mpeg4AudioConfig.IsValid() { - self.mpeg4AudioConfig, _ = isom.ReadADTSHeader(data) - } - // Skip ADTSHeader - if data[1]&0x1 == 0 { - if len(data) < 9 { - err = fmt.Errorf("ADTSHeader short read") - return - } - data = data[9:] - } else { - data = data[7:] - } - } - var filePos int64 sampleSize := len(data) if filePos, err = self.writeMdat(data); err != nil { From bac553e58e85669b219e9a564c077c7de38f6360 Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 2 Apr 2016 18:51:34 +0800 Subject: [PATCH 54/93] add IsADTSPayload() ExtractADTSPayload() --- isom/isom.go | 57 ++++++++++++++++++++++++++++++++++++++++++----- isom/isom_test.go | 6 ++--- 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/isom/isom.go b/isom/isom.go index 959ed17..ce7c1ec 100644 --- a/isom/isom.go +++ b/isom/isom.go @@ -81,13 +81,53 @@ var chanConfigTable = []int{ 0, 1, 2, 3, 4, 5, 6, 8, } -func ReadADTSHeader(data []byte) (objectType, sampleRateIndex, chanConfig, frameLength uint) { +func IsADTSPayload(frames []byte) bool { + return len(frames) > 7 && frames[0]==0xff&&frames[1]&0xf0==0xf0 +} + +func ReadADTSPayload(frames []byte) (config MPEG4AudioConfig, payload []byte, n int, next []byte, err error) { + if !IsADTSPayload(frames) { + err = fmt.Errorf("not adts frame") + return + } + config.ObjectType = uint(frames[2]>>6)+1 + config.SampleRateIndex = uint(frames[2]>>2&0xf) + config.ChannelConfig = uint(frames[2]<<2&0x4|frames[3]>>6&0x3) + framelen := int(frames[3]&0x3)<<11|int(frames[4])<<3|int(frames[5]>>5) + n = (int(frames[6]&0x3)+1)*1024 + hdrlen := 7 + if frames[1]&0x1 == 0 { + hdrlen = 9 + } + if framelen < hdrlen || len(frames) < framelen { + err = fmt.Errorf("invalid adts header length") + return + } + payload = append(payload, frames[hdrlen:framelen]...) + next = frames[framelen:] + return +} + +func ExtractADTSPayload(frames []byte) (config MPEG4AudioConfig, payload []byte, total int, err error) { + for len(frames) > 0 { + var n int + if config, payload, n, frames, err = ReadADTSPayload(frames); err != nil { + return + } + total += n + } + return +} + +func ReadADTSHeader(data []byte) (config MPEG4AudioConfig, frameLength int) { br := &bits.Reader{R: bytes.NewReader(data)} + var i uint //Structure //AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ) //Header consists of 7 or 9 bytes (without or with CRC). + // 2 bytes //A 12 syncword 0xFFF, all bits must be 1 br.ReadBits(12) //B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2 @@ -98,14 +138,14 @@ func ReadADTSHeader(data []byte) (objectType, sampleRateIndex, chanConfig, frame br.ReadBits(1) //E 2 profile, the MPEG-4 Audio Object Type minus 1 - objectType, _ = br.ReadBits(2) - objectType++ + config.ObjectType, _ = br.ReadBits(2) + config.ObjectType++ //F 4 MPEG-4 Sampling Frequency Index (15 is forbidden) - sampleRateIndex, _ = br.ReadBits(4) + config.SampleRateIndex, _ = br.ReadBits(4) //G 1 private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding br.ReadBits(1) //H 3 MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE) - chanConfig, _ = br.ReadBits(3) + config.ChannelConfig, _ = br.ReadBits(3) //I 1 originality, set to 0 when encoding, ignore when decoding br.ReadBits(1) //J 1 home, set to 0 when encoding, ignore when decoding @@ -116,7 +156,8 @@ func ReadADTSHeader(data []byte) (objectType, sampleRateIndex, chanConfig, frame br.ReadBits(1) //M 13 frame length, this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame) - frameLength, _ = br.ReadBits(13) + i, _ = br.ReadBits(13) + frameLength = int(i) //O 11 Buffer fullness br.ReadBits(11) //P 2 Number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame @@ -184,6 +225,10 @@ func writeSampleRateIndex(w *bits.Writer, index uint) (err error) { return } +func (self MPEG4AudioConfig) IsValid() bool { + return self.ObjectType > 0 +} + func (self MPEG4AudioConfig) Complete() (config MPEG4AudioConfig) { config = self if int(config.SampleRateIndex) < len(sampleRateTable) { diff --git a/isom/isom_test.go b/isom/isom_test.go index 6d81dfb..4bf32d7 100644 --- a/isom/isom_test.go +++ b/isom/isom_test.go @@ -35,12 +35,12 @@ func TestReadElemStreamDesc(t *testing.T) { if aconfig, err = ReadElemStreamDescAAC(bytes.NewReader(data)); err != nil { t.Error(err) } - t.Logf("aconfig=%x", aconfig.Complete()) + t.Logf("aconfig=%v", aconfig.Complete()) //00000000 ff f1 50 80 04 3f fc de 04 00 00 6c 69 62 66 61 |..P..?.....libfa| //00000010 61 63 20 31 2e 32 38 00 00 42 40 93 20 04 32 00 |ac 1.28..B@. .2.| //00000020 47 ff f1 50 80 05 1f fc 21 42 fe ed b2 5c a8 00 |G..P....!B...\..| data, _ = hex.DecodeString("fff15080043ffcde040000") - objectType, sampleRateIndex, chanConfig, frameLength := ReadADTSHeader(data) - t.Logf("objectType=%d sampleRateIndex=%d chanConfig=%d frameLength=%d", objectType, sampleRateIndex, chanConfig, frameLength) + aconfig, frameLength := ReadADTSHeader(data) + t.Logf("%v frameLength=%d", aconfig.Complete(), frameLength) } From 82b17612dd676e21d204d6fe03a47ff13bcc70ea Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 18 Apr 2016 00:20:01 +0800 Subject: [PATCH 55/93] add frag atom support --- atom/genStruct.js | 66 +++++++++ atom/otherStruct.go | 318 ++++++++++++++++++++++++++++++++++++++++---- atom/struct.go | 223 +++++++++++++++++++++++++++++++ 3 files changed, 581 insertions(+), 26 deletions(-) diff --git a/atom/genStruct.js b/atom/genStruct.js index 4c1c2c7..6a2122f 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -340,6 +340,72 @@ var atoms = { ], }, + movieFrag: { + cc4: 'moof', + fields: [ + ['$atoms', [ + ['header', '*movieFragHeader'], + ['tracks', '[]*trackFrag'], + ]], + ], + }, + + trackFragDecodeTime: { + cc4: 'tfdt', + }, + + movieFragHeader: { + cc4: 'mfhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['seqNum', 'int32'], + ], + }, + + trackFrag: { + cc4: 'traf', + fields: [ + ['$atoms', [ + ['header', '*trackFragHeader'], + ['decodeTime', '*trackFragDecodeTime'], + ['run', '*trackFragRun'], + ]], + ], + }, + + trackFragRun: { + cc4: 'trun', + }, + + trackFragHeader: { + cc4: 'tfhd', + }, + + /* + // need hand write + trackFragRun: { + cc4: 'trun', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['sampleCount', 'int32'], + ['dataOffset', 'int32'], + ['entries', '[]int32'], + ], + }, + + trackFragHeader: { + cc4: 'tfhd', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['id', 'int32'], + ['sampleDescriptionIndex', 'int32'], + ['_', '[12]byte'], + ], + }, + */ }; var DeclReadFunc = (opts) => { diff --git a/atom/otherStruct.go b/atom/otherStruct.go index ec73293..c5b298e 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -1,15 +1,15 @@ - package atom import ( - "io" - "fmt" "bytes" + "fmt" + "io" + "github.com/nareix/bits" ) type GolombBitReader struct { - R io.Reader - buf [1]byte + R io.Reader + buf [1]byte left byte } @@ -21,7 +21,7 @@ func (self *GolombBitReader) ReadBit() (res uint, err error) { self.left = 8 } self.left-- - res = uint(self.buf[0]>>self.left)&1 + res = uint(self.buf[0]>>self.left) & 1 return } @@ -31,7 +31,7 @@ func (self *GolombBitReader) ReadBits(n int) (res uint, err error) { if bit, err = self.ReadBit(); err != nil { return } - res |= bit< 0 { + flags = self.Flags + } else { + flags = self.FirstSampleFlags + } + + entry := TrackFragRunEntry{} + if flags&TRUN_SAMPLE_DURATION != 0 { + if entry.Duration, err = ReadInt(r, 4); err != nil { + return + } + } + if flags&TRUN_SAMPLE_SIZE != 0 { + if entry.Size, err = ReadInt(r, 4); err != nil { + return + } + } + if flags&TRUN_SAMPLE_FLAGS != 0 { + if entry.Flags, err = ReadInt(r, 4); err != nil { + return + } + } + if flags&TRUN_SAMPLE_CTS != 0 { + if entry.Cts, err = ReadInt(r, 4); err != nil { + return + } + } + + self.Entries = append(self.Entries, entry) + } + + res = self + return +} + +type TrackFragDecodeTime struct { + Version int + Flags int + Time int64 +} + +func ReadTrackFragDecodeTime(r *io.LimitedReader) (res *TrackFragDecodeTime, err error) { + + self := &TrackFragDecodeTime{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.Version != 0 { + if self.Time, err = bits.ReadInt64BE(r, 64); err != nil { + return + } + } else { + if self.Time, err = bits.ReadInt64BE(r, 32); err != nil { + return + } + } + res = self + return +} + +func WriteTrackFragDecodeTime(w io.WriteSeeker, self *TrackFragDecodeTime) (err error) { + var aw *Writer + if aw, err = WriteAtomHeader(w, "tfdt"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if self.Version != 0 { + if err = bits.WriteInt64BE(w, self.Time, 64); err != nil { + return + } + } else { + if err = bits.WriteInt64BE(w, self.Time, 32); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} + +func WalkTrackFragDecodeTime(w Walker, self *TrackFragDecodeTime) { + w.StartStruct("TrackFragDecodeTime") + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("Time") + w.Int64(self.Time) + w.EndStruct() + return +} + diff --git a/atom/struct.go b/atom/struct.go index 17128b5..3e75a92 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -2475,3 +2475,226 @@ func WalkChunkOffset(w Walker, self *ChunkOffset) { w.EndStruct() return } + +type MovieFrag struct { + Header *MovieFragHeader + Tracks []*TrackFrag +} + +func ReadMovieFrag(r *io.LimitedReader) (res *MovieFrag, err error) { + + self := &MovieFrag{} + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "mfhd": + { + if self.Header, err = ReadMovieFragHeader(ar); err != nil { + return + } + } + case "traf": + { + var item *TrackFrag + if item, err = ReadTrackFrag(ar); err != nil { + return + } + self.Tracks = append(self.Tracks, item) + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + res = self + return +} +func WriteMovieFrag(w io.WriteSeeker, self *MovieFrag) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "moof"); err != nil { + return + } + w = aw + if self.Header != nil { + if err = WriteMovieFragHeader(w, self.Header); err != nil { + return + } + } + if self.Tracks != nil { + for _, elem := range self.Tracks { + if err = WriteTrackFrag(w, elem); err != nil { + return + } + } + } + if err = aw.Close(); err != nil { + return + } + return +} +func WalkMovieFrag(w Walker, self *MovieFrag) { + + w.StartStruct("MovieFrag") + if self.Header != nil { + WalkMovieFragHeader(w, self.Header) + } + for i, item := range self.Tracks { + if w.FilterArrayItem("MovieFrag", "Tracks", i, len(self.Tracks)) { + if item != nil { + WalkTrackFrag(w, item) + } + } else { + w.ArrayLeft(i, len(self.Tracks)) + break + } + } + w.EndStruct() + return +} + +type MovieFragHeader struct { + Version int + Flags int + SeqNum int +} + +func ReadMovieFragHeader(r *io.LimitedReader) (res *MovieFragHeader, err error) { + + self := &MovieFragHeader{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.SeqNum, err = ReadInt(r, 4); err != nil { + return + } + res = self + return +} +func WriteMovieFragHeader(w io.WriteSeeker, self *MovieFragHeader) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "mfhd"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, self.SeqNum, 4); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} +func WalkMovieFragHeader(w Walker, self *MovieFragHeader) { + + w.StartStruct("MovieFragHeader") + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("SeqNum") + w.Int(self.SeqNum) + w.EndStruct() + return +} + +type TrackFrag struct { + Header *TrackFragHeader + DecodeTime *TrackFragDecodeTime + Run *TrackFragRun +} + +func ReadTrackFrag(r *io.LimitedReader) (res *TrackFrag, err error) { + + self := &TrackFrag{} + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "tfhd": + { + if self.Header, err = ReadTrackFragHeader(ar); err != nil { + return + } + } + case "tfdt": + { + if self.DecodeTime, err = ReadTrackFragDecodeTime(ar); err != nil { + return + } + } + case "trun": + { + if self.Run, err = ReadTrackFragRun(ar); err != nil { + return + } + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + res = self + return +} +func WriteTrackFrag(w io.WriteSeeker, self *TrackFrag) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "traf"); err != nil { + return + } + w = aw + if self.Header != nil { + if err = WriteTrackFragHeader(w, self.Header); err != nil { + return + } + } + if self.DecodeTime != nil { + if err = WriteTrackFragDecodeTime(w, self.DecodeTime); err != nil { + return + } + } + if self.Run != nil { + if err = WriteTrackFragRun(w, self.Run); err != nil { + return + } + } + if err = aw.Close(); err != nil { + return + } + return +} +func WalkTrackFrag(w Walker, self *TrackFrag) { + + w.StartStruct("TrackFrag") + if self.Header != nil { + WalkTrackFragHeader(w, self.Header) + } + if self.DecodeTime != nil { + WalkTrackFragDecodeTime(w, self.DecodeTime) + } + if self.Run != nil { + WalkTrackFragRun(w, self.Run) + } + w.EndStruct() + return +} From 35097b942c38c3a44bd6a11f1f9bb052d2ecdde7 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 18 Apr 2016 00:20:17 +0800 Subject: [PATCH 56/93] add dumper Int64() support --- atom/dumper.go | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/atom/dumper.go b/atom/dumper.go index c18f911..3f28e56 100644 --- a/atom/dumper.go +++ b/atom/dumper.go @@ -15,10 +15,13 @@ type Walker interface { EndStruct() Name(string) Int(int) + Int64(int64) + HexInt(int) Fixed(Fixed) String(string) Bytes([]byte) TimeStamp(TimeStamp) + Println(msg ...interface{}) } type Dumper struct { @@ -33,12 +36,12 @@ func (self Dumper) tab() string { return strings.Repeat(" ", self.depth*2) } -func (self Dumper) println(msg string) { - fmt.Fprintln(self.W, self.tab()+msg) +func (self Dumper) Println(msg ...interface{}) { + fmt.Fprintln(self.W, self.tab()+fmt.Sprint(msg...)) } func (self *Dumper) ArrayLeft(i int, n int) { - self.println(fmt.Sprintf("... total %d elements", n)) + self.Println(fmt.Sprintf("... total %d elements", n)) } func (self *Dumper) FilterArrayItem(name string, field string, i int, n int) bool { @@ -53,7 +56,7 @@ func (self *Dumper) EndArray() { func (self *Dumper) StartStruct(name string) { self.depth++ - self.println(fmt.Sprintf("[%s]", name)) + self.Println(fmt.Sprintf("[%s]", name)) } func (self *Dumper) EndStruct() { @@ -65,22 +68,30 @@ func (self *Dumper) Name(name string) { } func (self Dumper) Int(val int) { - self.println(fmt.Sprintf("%s: %d", self.name, val)) + self.Int64(int64(val)) +} + +func (self Dumper) Int64(val int64) { + self.Println(fmt.Sprintf("%s: %d", self.name, val)) +} + +func (self Dumper) HexInt(val int) { + self.Println(fmt.Sprintf("%s: %x", self.name, val)) } func (self Dumper) String(val string) { - self.println(fmt.Sprintf("%s: %s", self.name, val)) + self.Println(fmt.Sprintf("%s: %s", self.name, val)) } func (self Dumper) Fixed(val Fixed) { - self.println(fmt.Sprintf("%s: %d", self.name, FixedToInt(val))) + self.Println(fmt.Sprintf("%s: %d", self.name, FixedToInt(val))) } func (self Dumper) Bytes(val []byte) { - self.println(fmt.Sprintf("%s: %s", self.name, hex.EncodeToString(val))) + self.Println(fmt.Sprintf("%s: %s", self.name, hex.EncodeToString(val))) } func (self Dumper) TimeStamp(val TimeStamp) { - self.println(fmt.Sprintf("%s: %d", self.name, int(val))) + self.Println(fmt.Sprintf("%s: %d", self.name, int(val))) } From 15a2ce0a4ec4b910a9a3699d6d828f8cdfaef252 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 18 Apr 2016 00:21:26 +0800 Subject: [PATCH 57/93] rename funcs add MakeADTSHeader() --- isom/isom.go | 48 +++++++++++++++++++++++++++++++---------------- isom/isom_test.go | 11 +++++++++-- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/isom/isom.go b/isom/isom.go index ce7c1ec..64197c1 100644 --- a/isom/isom.go +++ b/isom/isom.go @@ -81,40 +81,56 @@ var chanConfigTable = []int{ 0, 1, 2, 3, 4, 5, 6, 8, } -func IsADTSPayload(frames []byte) bool { +func IsADTSFrame(frames []byte) bool { return len(frames) > 7 && frames[0]==0xff&&frames[1]&0xf0==0xf0 } -func ReadADTSPayload(frames []byte) (config MPEG4AudioConfig, payload []byte, n int, next []byte, err error) { - if !IsADTSPayload(frames) { +func ReadADTSFrame(frame []byte) (config MPEG4AudioConfig, payload []byte, samples int, framelen int, err error) { + if !IsADTSFrame(frame) { err = fmt.Errorf("not adts frame") return } - config.ObjectType = uint(frames[2]>>6)+1 - config.SampleRateIndex = uint(frames[2]>>2&0xf) - config.ChannelConfig = uint(frames[2]<<2&0x4|frames[3]>>6&0x3) - framelen := int(frames[3]&0x3)<<11|int(frames[4])<<3|int(frames[5]>>5) - n = (int(frames[6]&0x3)+1)*1024 + config.ObjectType = uint(frame[2]>>6)+1 + config.SampleRateIndex = uint(frame[2]>>2&0xf) + config.ChannelConfig = uint(frame[2]<<2&0x4|frame[3]>>6&0x3) + framelen = int(frame[3]&0x3)<<11|int(frame[4])<<3|int(frame[5]>>5) + samples = (int(frame[6]&0x3)+1)*1024 hdrlen := 7 - if frames[1]&0x1 == 0 { + if frame[1]&0x1 == 0 { hdrlen = 9 } - if framelen < hdrlen || len(frames) < framelen { + if framelen < hdrlen || len(frame) < framelen { err = fmt.Errorf("invalid adts header length") return } - payload = append(payload, frames[hdrlen:framelen]...) - next = frames[framelen:] + payload = frame[hdrlen:framelen] return } -func ExtractADTSPayload(frames []byte) (config MPEG4AudioConfig, payload []byte, total int, err error) { +func MakeADTSHeader(config MPEG4AudioConfig, samples int, payloadLength int) (header []byte) { + payloadLength += 7 + //AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ) + header = []byte{0xff,0xf1,0x50,0x80,0x043,0xff,0xcd} + //config.ObjectType = uint(frames[2]>>6)+1 + //config.SampleRateIndex = uint(frames[2]>>2&0xf) + //config.ChannelConfig = uint(frames[2]<<2&0x4|frames[3]>>6&0x3) + header[2] = (byte(config.ObjectType-1)&0x3)<<6|(byte(config.SampleRateIndex)&0xf)<<2|byte(config.ChannelConfig>>2)&0x1 + header[3] = header[3]&0x3f|byte(config.ChannelConfig&0x3)<<6 + header[3] = header[3]&0xfc|byte(payloadLength>>11)&0x3 + header[4] = byte(payloadLength>>3) + header[5] = header[5]&0x1f|(byte(payloadLength)&0x7)<<5 + header[6] = header[6]&0xfc|byte(samples/1024-1) + return +} + +func ExtractADTSFrames(frames []byte) (config MPEG4AudioConfig, payload []byte, samples int, err error) { for len(frames) > 0 { - var n int - if config, payload, n, frames, err = ReadADTSPayload(frames); err != nil { + var n, framelen int + if config, payload, n, framelen, err = ReadADTSFrame(frames); err != nil { return } - total += n + frames = frames[framelen:] + samples += n } return } diff --git a/isom/isom_test.go b/isom/isom_test.go index 4bf32d7..29903b1 100644 --- a/isom/isom_test.go +++ b/isom/isom_test.go @@ -41,6 +41,13 @@ func TestReadElemStreamDesc(t *testing.T) { //00000010 61 63 20 31 2e 32 38 00 00 42 40 93 20 04 32 00 |ac 1.28..B@. .2.| //00000020 47 ff f1 50 80 05 1f fc 21 42 fe ed b2 5c a8 00 |G..P....!B...\..| data, _ = hex.DecodeString("fff15080043ffcde040000") - aconfig, frameLength := ReadADTSHeader(data) - t.Logf("%v frameLength=%d", aconfig.Complete(), frameLength) + var n, framelen int + aconfig, _, n, _, _ = ReadADTSFrame(data) + t.Logf("%v n=%d", aconfig.Complete(), n) + + data = MakeADTSHeader(aconfig, 1024*3, 33) + data = append(data, []byte{1,2,3,4,5}...) + t.Logf("%x", data) + aconfig, _, n, framelen, err = ReadADTSFrame(data) + t.Logf("%v n=%d framelen=%d err=%v", aconfig.Complete(), n, framelen, err) } From 897221b6a614236f495eb7c27f084fde40c05108 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 18 Apr 2016 14:24:50 +0800 Subject: [PATCH 58/93] move golomb reader to other packet --- atom/otherStruct.go | 86 +++++++-------------------------------------- 1 file changed, 13 insertions(+), 73 deletions(-) diff --git a/atom/otherStruct.go b/atom/otherStruct.go index c5b298e..77e6093 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -7,66 +7,6 @@ import ( "github.com/nareix/bits" ) -type GolombBitReader struct { - R io.Reader - buf [1]byte - left byte -} - -func (self *GolombBitReader) ReadBit() (res uint, err error) { - if self.left == 0 { - if _, err = self.R.Read(self.buf[:]); err != nil { - return - } - self.left = 8 - } - self.left-- - res = uint(self.buf[0]>>self.left) & 1 - return -} - -func (self *GolombBitReader) ReadBits(n int) (res uint, err error) { - for i := 0; i < n; i++ { - var bit uint - if bit, err = self.ReadBit(); err != nil { - return - } - res |= bit << uint(n-i-1) - } - return -} - -func (self *GolombBitReader) ReadExponentialGolombCode() (res uint, err error) { - i := 0 - for { - var bit uint - if bit, err = self.ReadBit(); err != nil { - return - } - if !(bit == 0 && i < 32) { - break - } - i++ - } - if res, err = self.ReadBits(i); err != nil { - return - } - res += (1 << uint(i)) - 1 - return -} - -func (self *GolombBitReader) ReadSE() (res uint, err error) { - if res, err = self.ReadExponentialGolombCode(); err != nil { - return - } - if res&0x01 != 0 { - res = (res + 1) / 2 - } else { - res = -res / 2 - } - return -} - type H264SPSInfo struct { ProfileIdc uint LevelIdc uint @@ -84,7 +24,7 @@ type H264SPSInfo struct { } func ParseH264SPS(data []byte) (res *H264SPSInfo, err error) { - r := &GolombBitReader{ + r := &bits.GolombBitReader{ R: bytes.NewReader(data), } @@ -285,18 +225,6 @@ type AVCDecoderConfRecord struct { PPS [][]byte } -func WriteSampleByNALU(w io.Writer, nalu []byte) (size int, err error) { - if err = WriteInt(w, len(nalu), 4); err != nil { - return - } - size += 4 - if _, err = w.Write(nalu); err != nil { - return - } - size += len(nalu) - return -} - func CreateAVCDecoderConfRecord( SPS []byte, PPS []byte, @@ -412,6 +340,18 @@ func ReadAVCDecoderConfRecord(r *io.LimitedReader) (self AVCDecoderConfRecord, e return } +func WriteSampleByNALU(w io.Writer, nalu []byte) (size int, err error) { + if err = WriteInt(w, len(nalu), 4); err != nil { + return + } + size += 4 + if _, err = w.Write(nalu); err != nil { + return + } + size += len(nalu) + return +} + const ( TFHD_BASE_DATA_OFFSET = 0x01 TFHD_STSD_ID = 0x02 From cb12a946ffa15c5709b9f31458fadba623778540 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 18 Apr 2016 14:25:23 +0800 Subject: [PATCH 59/93] add adts aac auto convert --- muxer.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/muxer.go b/muxer.go index e664f7e..d4c40b0 100644 --- a/muxer.go +++ b/muxer.go @@ -145,7 +145,33 @@ func (self *Track) SetTimeScale(timeScale int64) { return } -func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { +func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, frame []byte) (err error) { + if self.Type == AAC && isom.IsADTSFrame(frame) { + config := self.mpeg4AudioConfig.Complete() + if config.SampleRate == 0 { + err = fmt.Errorf("invalid sample rate") + return + } + for len(frame) > 0 { + var payload []byte + var samples int + var framelen int + if _, payload, samples, framelen, err = isom.ReadADTSFrame(frame); err != nil { + return + } + delta := int64(samples)*self.TimeScale()/int64(config.SampleRate) + pts += delta + dts += delta + frame = frame[framelen:] + if self.writeSample(pts, dts, isKeyFrame, payload); err != nil { + return + } + } + } + return self.writeSample(pts, dts, isKeyFrame, frame) +} + +func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { var filePos int64 sampleSize := len(data) if filePos, err = self.writeMdat(data); err != nil { From 371d0f13e3c2d45f10f538d2d8eacca293a01db1 Mon Sep 17 00:00:00 2001 From: nareix Date: Mon, 18 Apr 2016 17:45:15 +0800 Subject: [PATCH 60/93] add example --- example/example.go | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/example/example.go b/example/example.go index 3b0e1e2..df07b6d 100644 --- a/example/example.go +++ b/example/example.go @@ -10,9 +10,7 @@ import ( func DemuxExample() { file, _ := os.Open("test.mp4") - demuxer := &mp4.Demuxer{ - R: file, - } + demuxer := &mp4.Demuxer{R: file} demuxer.ReadHeader() fmt.Println("Total tracks: ", len(demuxer.Tracks)) @@ -57,7 +55,35 @@ func DemuxExample() { fmt.Print(hex.Dump(sample)) } -func main() { - DemuxExample() +func MuxExample() { + infile, _ := os.Open("test.mp4") + outfile, _ := os.Create("test.out.mp4") + + demuxer := &mp4.Demuxer{R: infile} + demuxer.ReadHeader() + + muxer := &mp4.Muxer{W: outfile} + muxer.AddH264Track() + muxer.TrackH264.SetH264PPS(demuxer.TrackH264.GetH264PPS()) + muxer.TrackH264.SetH264SPS(demuxer.TrackH264.GetH264SPS()) + muxer.TrackH264.SetTimeScale(demuxer.TrackH264.TimeScale()) + + muxer.WriteHeader() + for { + pts, dts, isKeyFrame, data, err := demuxer.TrackH264.ReadSample() + if err != nil { + break + } + fmt.Println("sample #", dts, pts) + muxer.TrackH264.WriteSample(pts, dts, isKeyFrame, data) + } + + muxer.WriteTrailer() + outfile.Close() +} + +func main() { + //DemuxExample() + MuxExample() } From 07ed4e909845b79e004bcb53ae5a1c9cae55ebfd Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 19 Apr 2016 08:56:08 +0800 Subject: [PATCH 61/93] replace Track to Stream --- atom/atom.go | 2 - atom/dumper.go | 18 +- atom/otherStruct.go | 21 +- atom/reader.go | 6 +- atom/struct.go | 16 +- atom/types.go | 6 +- atom/utils.go | 10 +- atom/writer.go | 10 +- demuxer.go | 139 ++++++------- example/example.go | 6 +- isom/isom.go | 26 +-- isom/isom_test.go | 2 +- muxer.go | 477 +++++++++++--------------------------------- track.go | 47 ----- 14 files changed, 225 insertions(+), 561 deletions(-) delete mode 100644 track.go diff --git a/atom/atom.go b/atom/atom.go index b97bac0..f92cbab 100644 --- a/atom/atom.go +++ b/atom/atom.go @@ -1,3 +1 @@ - package atom - diff --git a/atom/dumper.go b/atom/dumper.go index 3f28e56..262ed13 100644 --- a/atom/dumper.go +++ b/atom/dumper.go @@ -1,16 +1,15 @@ - package atom import ( - "io" - "fmt" - "strings" "encoding/hex" + "fmt" + "io" + "strings" ) type Walker interface { - FilterArrayItem(string,string,int,int) bool - ArrayLeft(int,int) + FilterArrayItem(string, string, int, int) bool + ArrayLeft(int, int) StartStruct(string) EndStruct() Name(string) @@ -25,9 +24,9 @@ type Walker interface { } type Dumper struct { - W io.Writer - depth int - name string + W io.Writer + depth int + name string arrlen int arridx int } @@ -94,4 +93,3 @@ func (self Dumper) Bytes(val []byte) { func (self Dumper) TimeStamp(val TimeStamp) { self.Println(fmt.Sprintf("%s: %d", self.name, int(val))) } - diff --git a/atom/otherStruct.go b/atom/otherStruct.go index 77e6093..2c5d154 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -3,8 +3,8 @@ package atom import ( "bytes" "fmt" - "io" "github.com/nareix/bits" + "io" ) type H264SPSInfo struct { @@ -363,14 +363,14 @@ const ( ) type TrackFragHeader struct { - Version int - Flags int - Id int - DefaultSize int - DefaultDuration int - DefaultFlags int - BaseDataOffset int64 - StsdId int + Version int + Flags int + Id int + DefaultSize int + DefaultDuration int + DefaultFlags int + BaseDataOffset int64 + StsdId int } func WalkTrackFragHeader(w Walker, self *TrackFragHeader) { @@ -431,7 +431,7 @@ func ReadTrackFragHeader(r *io.LimitedReader) (res *TrackFragHeader, err error) } if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - if self.DefaultFlags,err = ReadInt(r, 4); err != nil { + if self.DefaultFlags, err = ReadInt(r, 4); err != nil { return } } @@ -617,4 +617,3 @@ func WalkTrackFragDecodeTime(w Walker, self *TrackFragDecodeTime) { w.EndStruct() return } - diff --git a/atom/reader.go b/atom/reader.go index afaea0a..b439bab 100644 --- a/atom/reader.go +++ b/atom/reader.go @@ -1,4 +1,3 @@ - package atom import ( @@ -33,7 +32,7 @@ func ReadInt(r io.Reader, n int) (res int, err error) { return } if uval&(1<>16) + return int(val >> 16) } - diff --git a/atom/utils.go b/atom/utils.go index 5e7e792..3f16f8b 100644 --- a/atom/utils.go +++ b/atom/utils.go @@ -1,8 +1,7 @@ - package atom -func GetAVCDecoderConfRecordByTrack(track *Track) (record *AVCDecoderConfRecord) { - if media := track.Media; media != nil { +func GetAVCDecoderConfRecordByTrack(stream *Track) (record *AVCDecoderConfRecord) { + if media := stream.Media; media != nil { if info := media.Info; info != nil { if sample := info.Sample; sample != nil { if desc := sample.SampleDesc; desc != nil { @@ -18,8 +17,8 @@ func GetAVCDecoderConfRecordByTrack(track *Track) (record *AVCDecoderConfRecord) return } -func GetMp4aDescByTrack(track *Track) (mp4a *Mp4aDesc) { - if media := track.Media; media != nil { +func GetMp4aDescByTrack(stream *Track) (mp4a *Mp4aDesc) { + if media := stream.Media; media != nil { if info := media.Info; info != nil { if sample := info.Sample; sample != nil { if desc := sample.SampleDesc; desc != nil { @@ -32,4 +31,3 @@ func GetMp4aDescByTrack(track *Track) (mp4a *Mp4aDesc) { } return } - diff --git a/atom/writer.go b/atom/writer.go index 6ddbc96..0fd08cc 100644 --- a/atom/writer.go +++ b/atom/writer.go @@ -1,4 +1,3 @@ - package atom import ( @@ -16,7 +15,7 @@ func WriteBytes(w io.Writer, b []byte, n int) (err error) { func WriteUInt(w io.Writer, val uint, n int) (err error) { var b [8]byte - for i := n-1; i >= 0; i-- { + for i := n - 1; i >= 0; i-- { b[i] = byte(val) val >>= 8 } @@ -26,7 +25,7 @@ func WriteUInt(w io.Writer, val uint, n int) (err error) { func WriteInt(w io.Writer, val int, n int) (err error) { var uval uint if val < 0 { - uval = uint((1<>8 + uval = uint(val) >> 8 } else if n == 4 { uval = uint(val) } else { @@ -96,7 +95,7 @@ func (self *Writer) Close() (err error) { if curPos, err = self.Seek(0, 1); err != nil { return } - if err = RefillInt(self, self.sizePos, int(curPos - self.sizePos), 4); err != nil { + if err = RefillInt(self, self.sizePos, int(curPos-self.sizePos), 4); err != nil { return } if false { @@ -118,4 +117,3 @@ func WriteAtomHeader(w io.WriteSeeker, cc4 string) (res *Writer, err error) { res = self return } - diff --git a/demuxer.go b/demuxer.go index 7d1b6df..315c798 100644 --- a/demuxer.go +++ b/demuxer.go @@ -1,20 +1,19 @@ - package mp4 import ( - "github.com/nareix/mp4/atom" - "github.com/nareix/mp4/isom" "bytes" "fmt" + "github.com/nareix/av" + "github.com/nareix/mp4/atom" + "github.com/nareix/mp4/isom" "io" ) type Demuxer struct { R io.ReadSeeker - Tracks []*Track - TrackH264 *Track - TrackAAC *Track - MovieAtom *atom.Movie + + streams []*Stream + movieAtom *atom.Movie } func (self *Demuxer) ReadHeader() (err error) { @@ -52,36 +51,35 @@ func (self *Demuxer) ReadHeader() (err error) { err = fmt.Errorf("'moov' atom not found") return } - self.MovieAtom = moov + self.movieAtom = moov - self.Tracks = []*Track{} - for _, atrack := range(moov.Tracks) { - track := &Track{ - TrackAtom: atrack, - r: self.R, + self.streams = []*Stream{} + for _, atrack := range moov.Tracks { + stream := &Stream{ + trackAtom: atrack, + r: self.R, } if atrack.Media != nil && atrack.Media.Info != nil && atrack.Media.Info.Sample != nil { - track.sample = atrack.Media.Info.Sample + stream.sample = atrack.Media.Info.Sample + stream.SetTimeScale(int64(atrack.Media.Header.TimeScale)) } else { err = fmt.Errorf("sample table not found") return } if record := atom.GetAVCDecoderConfRecordByTrack(atrack); record != nil { if len(record.PPS) > 0 { - track.pps = record.PPS[0] + stream.pps = record.PPS[0] } if len(record.SPS) > 0 { - track.sps = record.SPS[0] + stream.sps = record.SPS[0] } - track.Type = H264 - self.TrackH264 = track - self.Tracks = append(self.Tracks, track) + stream.SetType(av.H264) + self.streams = append(self.streams, stream) } else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil && mp4a.Conf != nil { if config, err := isom.ReadElemStreamDescAAC(bytes.NewReader(mp4a.Conf.Data)); err == nil { - track.mpeg4AudioConfig = config.Complete() - track.Type = AAC - self.TrackAAC = track - self.Tracks = append(self.Tracks, track) + stream.mpeg4AudioConfig = config.Complete() + stream.SetType(av.AAC) + self.streams = append(self.streams, stream) } } } @@ -89,16 +87,16 @@ func (self *Demuxer) ReadHeader() (err error) { return } -func (self *Track) setSampleIndex(index int) (err error) { +func (self *Stream) setSampleIndex(index int) (err error) { found := false start := 0 self.chunkGroupIndex = 0 - for self.chunkIndex = range(self.sample.ChunkOffset.Entries) { + for self.chunkIndex = range self.sample.ChunkOffset.Entries { n := self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk if index >= start && index < start+n { found = true - self.sampleIndexInChunk = index-start + self.sampleIndexInChunk = index - start break } start += n @@ -113,14 +111,14 @@ func (self *Track) setSampleIndex(index int) (err error) { } if self.sample.SampleSize.SampleSize != 0 { - self.sampleOffsetInChunk = int64(self.sampleIndexInChunk*self.sample.SampleSize.SampleSize) + self.sampleOffsetInChunk = int64(self.sampleIndexInChunk * self.sample.SampleSize.SampleSize) } else { if index >= len(self.sample.SampleSize.Entries) { err = io.EOF return } self.sampleOffsetInChunk = int64(0) - for i := index-self.sampleIndexInChunk; i < index; i++ { + for i := index - self.sampleIndexInChunk; i < index; i++ { self.sampleOffsetInChunk += int64(self.sample.SampleSize.Entries[i]) } } @@ -133,12 +131,12 @@ func (self *Track) setSampleIndex(index int) (err error) { entry := self.sample.TimeToSample.Entries[self.sttsEntryIndex] n := entry.Count if index >= start && index < start+n { - self.sampleIndexInSttsEntry = index-start - self.dts += int64((index-start)*entry.Duration) + self.sampleIndexInSttsEntry = index - start + self.dts += int64((index - start) * entry.Duration) break } start += n - self.dts += int64(n*entry.Duration) + self.dts += int64(n * entry.Duration) self.sttsEntryIndex++ } if !found { @@ -153,7 +151,7 @@ func (self *Track) setSampleIndex(index int) (err error) { for self.cttsEntryIndex < len(self.sample.CompositionOffset.Entries) { n := self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count if index >= start && index < start+n { - self.sampleIndexInCttsEntry = index-start + self.sampleIndexInCttsEntry = index - start break } start += n @@ -179,7 +177,7 @@ func (self *Track) setSampleIndex(index int) (err error) { return } -func (self *Track) isSampleValid() bool { +func (self *Stream) isSampleValid() bool { if self.chunkIndex >= len(self.sample.ChunkOffset.Entries) { return false } @@ -207,7 +205,7 @@ func (self *Track) isSampleValid() bool { return true } -func (self *Track) incSampleIndex() { +func (self *Stream) incSampleIndex() { self.sampleIndexInChunk++ if self.sampleIndexInChunk == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk { self.chunkIndex++ @@ -252,11 +250,11 @@ func (self *Track) incSampleIndex() { self.sampleIndex++ } -func (self *Track) SampleCount() int { +func (self *Stream) SampleCount() int { if self.sample.SampleSize.SampleSize == 0 { chunkGroupIndex := 0 count := 0 - for chunkIndex := range(self.sample.ChunkOffset.Entries) { + for chunkIndex := range self.sample.ChunkOffset.Entries { n := self.sample.SampleToChunk.Entries[chunkGroupIndex].SamplesPerChunk count += n if chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) && @@ -270,7 +268,11 @@ func (self *Track) SampleCount() int { } } -func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { +func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { + return +} + +func (self *Stream) readSample() (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { if !self.isSampleValid() { err = io.EOF return @@ -284,7 +286,7 @@ func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []b sampleSize = self.sample.SampleSize.Entries[self.sampleIndex] } - sampleOffset := int64(chunkOffset)+self.sampleOffsetInChunk + sampleOffset := int64(chunkOffset) + self.sampleOffsetInChunk if _, err = self.r.Seek(int64(sampleOffset), 0); err != nil { return } @@ -303,7 +305,7 @@ func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []b //println("pts/dts", self.ptsEntryIndex, self.dtsEntryIndex) dts = self.dts if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 { - pts = self.dts+int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset) + pts = self.dts + int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset) } else { pts = dts } @@ -312,36 +314,28 @@ func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []b return } -func (self *Track) Duration() float64 { +func (self *Stream) Duration() float64 { total := int64(0) - for _, entry := range(self.sample.TimeToSample.Entries) { - total += int64(entry.Duration*entry.Count) + for _, entry := range self.sample.TimeToSample.Entries { + total += int64(entry.Duration * entry.Count) } - return float64(total)/float64(self.TrackAtom.Media.Header.TimeScale) + return float64(total) / float64(self.trackAtom.Media.Header.TimeScale) } -func (self *Track) CurTime() float64 { - return self.TsToTime(self.dts) -} - -func (self *Track) CurTs() int64 { - return self.dts -} - -func (self *Track) CurSampleIndex() int { +func (self *Stream) CurSampleIndex() int { return self.sampleIndex } -func (self *Track) SeekToTime(time float64) error { +func (self *Stream) SeekToTime(time float64) error { index := self.TimeToSampleIndex(time) return self.setSampleIndex(index) } -func (self *Track) SeekToSampleIndex(index int) error { +func (self *Stream) SeekToSampleIndex(index int) error { return self.setSampleIndex(index) } -func (self *Track) TimeToSampleIndex(time float64) int { +func (self *Stream) TimeToSampleIndex(time float64) int { targetTs := self.TimeToTs(time) targetIndex := 0 @@ -350,11 +344,11 @@ func (self *Track) TimeToSampleIndex(time float64) int { startIndex := 0 endIndex := 0 found := false - for _, entry := range(self.sample.TimeToSample.Entries) { - endTs = startTs+int64(entry.Count*entry.Duration) - endIndex = startIndex+entry.Count + for _, entry := range self.sample.TimeToSample.Entries { + endTs = startTs + int64(entry.Count*entry.Duration) + endIndex = startIndex + entry.Count if targetTs >= startTs && targetTs < endTs { - targetIndex = startIndex+int((targetTs-startTs)/int64(entry.Duration)) + targetIndex = startIndex + int((targetTs-startTs)/int64(entry.Duration)) found = true } startTs = endTs @@ -364,15 +358,15 @@ func (self *Track) TimeToSampleIndex(time float64) int { if targetTs < 0 { targetIndex = 0 } else { - targetIndex = endIndex-1 + targetIndex = endIndex - 1 } } if self.sample.SyncSample != nil { entries := self.sample.SyncSample.Entries - for i := len(entries)-1; i >= 0; i-- { + for i := len(entries) - 1; i >= 0; i-- { if entries[i]-1 < targetIndex { - targetIndex = entries[i]-1 + targetIndex = entries[i] - 1 break } } @@ -380,24 +374,3 @@ func (self *Track) TimeToSampleIndex(time float64) int { return targetIndex } - -func (self *Track) TimeToTs(time float64) int64 { - return int64(time*float64(self.TrackAtom.Media.Header.TimeScale)) -} - -func (self *Track) TsToTime(ts int64) float64 { - return float64(ts)/float64(self.TrackAtom.Media.Header.TimeScale) -} - -func (self *Track) TimeScale() int64 { - return int64(self.TrackAtom.Media.Header.TimeScale) -} - -func (self *Track) GetH264PPSAndSPS() (pps, sps []byte) { - return self.pps, self.sps -} - -func (self *Track) GetMPEG4AudioConfig() isom.MPEG4AudioConfig { - return self.mpeg4AudioConfig -} - diff --git a/example/example.go b/example/example.go index df07b6d..61510df 100644 --- a/example/example.go +++ b/example/example.go @@ -1,11 +1,10 @@ - package main import ( + "encoding/hex" + "fmt" "github.com/nareix/mp4" "os" - "fmt" - "encoding/hex" ) func DemuxExample() { @@ -86,4 +85,3 @@ func main() { //DemuxExample() MuxExample() } - diff --git a/isom/isom.go b/isom/isom.go index 64197c1..f81e3af 100644 --- a/isom/isom.go +++ b/isom/isom.go @@ -82,7 +82,7 @@ var chanConfigTable = []int{ } func IsADTSFrame(frames []byte) bool { - return len(frames) > 7 && frames[0]==0xff&&frames[1]&0xf0==0xf0 + return len(frames) > 7 && frames[0] == 0xff && frames[1]&0xf0 == 0xf0 } func ReadADTSFrame(frame []byte) (config MPEG4AudioConfig, payload []byte, samples int, framelen int, err error) { @@ -90,11 +90,11 @@ func ReadADTSFrame(frame []byte) (config MPEG4AudioConfig, payload []byte, sampl err = fmt.Errorf("not adts frame") return } - config.ObjectType = uint(frame[2]>>6)+1 - config.SampleRateIndex = uint(frame[2]>>2&0xf) - config.ChannelConfig = uint(frame[2]<<2&0x4|frame[3]>>6&0x3) - framelen = int(frame[3]&0x3)<<11|int(frame[4])<<3|int(frame[5]>>5) - samples = (int(frame[6]&0x3)+1)*1024 + config.ObjectType = uint(frame[2]>>6) + 1 + config.SampleRateIndex = uint(frame[2] >> 2 & 0xf) + config.ChannelConfig = uint(frame[2]<<2&0x4 | frame[3]>>6&0x3) + framelen = int(frame[3]&0x3)<<11 | int(frame[4])<<3 | int(frame[5]>>5) + samples = (int(frame[6]&0x3) + 1) * 1024 hdrlen := 7 if frame[1]&0x1 == 0 { hdrlen = 9 @@ -110,16 +110,16 @@ func ReadADTSFrame(frame []byte) (config MPEG4AudioConfig, payload []byte, sampl func MakeADTSHeader(config MPEG4AudioConfig, samples int, payloadLength int) (header []byte) { payloadLength += 7 //AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ) - header = []byte{0xff,0xf1,0x50,0x80,0x043,0xff,0xcd} + header = []byte{0xff, 0xf1, 0x50, 0x80, 0x043, 0xff, 0xcd} //config.ObjectType = uint(frames[2]>>6)+1 //config.SampleRateIndex = uint(frames[2]>>2&0xf) //config.ChannelConfig = uint(frames[2]<<2&0x4|frames[3]>>6&0x3) - header[2] = (byte(config.ObjectType-1)&0x3)<<6|(byte(config.SampleRateIndex)&0xf)<<2|byte(config.ChannelConfig>>2)&0x1 - header[3] = header[3]&0x3f|byte(config.ChannelConfig&0x3)<<6 - header[3] = header[3]&0xfc|byte(payloadLength>>11)&0x3 - header[4] = byte(payloadLength>>3) - header[5] = header[5]&0x1f|(byte(payloadLength)&0x7)<<5 - header[6] = header[6]&0xfc|byte(samples/1024-1) + header[2] = (byte(config.ObjectType-1)&0x3)<<6 | (byte(config.SampleRateIndex)&0xf)<<2 | byte(config.ChannelConfig>>2)&0x1 + header[3] = header[3]&0x3f | byte(config.ChannelConfig&0x3)<<6 + header[3] = header[3]&0xfc | byte(payloadLength>>11)&0x3 + header[4] = byte(payloadLength >> 3) + header[5] = header[5]&0x1f | (byte(payloadLength)&0x7)<<5 + header[6] = header[6]&0xfc | byte(samples/1024-1) return } diff --git a/isom/isom_test.go b/isom/isom_test.go index 29903b1..7af8b16 100644 --- a/isom/isom_test.go +++ b/isom/isom_test.go @@ -46,7 +46,7 @@ func TestReadElemStreamDesc(t *testing.T) { t.Logf("%v n=%d", aconfig.Complete(), n) data = MakeADTSHeader(aconfig, 1024*3, 33) - data = append(data, []byte{1,2,3,4,5}...) + data = append(data, []byte{1, 2, 3, 4, 5}...) t.Logf("%x", data) aconfig, _, n, framelen, err = ReadADTSFrame(data) t.Logf("%v n=%d framelen=%d err=%v", aconfig.Complete(), n, framelen, err) diff --git a/muxer.go b/muxer.go index d4c40b0..bb144f3 100644 --- a/muxer.go +++ b/muxer.go @@ -1,57 +1,54 @@ - package mp4 import ( + "bytes" + "fmt" + "github.com/nareix/av" "github.com/nareix/mp4/atom" "github.com/nareix/mp4/isom" "io" - "bytes" - "fmt" ) type Muxer struct { - W io.WriteSeeker - Tracks []*Track - TrackH264 *Track - TrackAAC *Track - + W io.WriteSeeker + streams []*Stream mdatWriter *atom.Writer } -func (self *Muxer) newTrack() *Track { - track := &Track{} +func (self *Muxer) NewStream() av.Stream { + stream := &Stream{} - track.sample = &atom.SampleTable{ - SampleDesc: &atom.SampleDesc{}, + stream.sample = &atom.SampleTable{ + SampleDesc: &atom.SampleDesc{}, TimeToSample: &atom.TimeToSample{}, SampleToChunk: &atom.SampleToChunk{ Entries: []atom.SampleToChunkEntry{ { - FirstChunk: 1, - SampleDescId: 1, + FirstChunk: 1, + SampleDescId: 1, SamplesPerChunk: 1, }, }, }, - SampleSize: &atom.SampleSize{}, + SampleSize: &atom.SampleSize{}, ChunkOffset: &atom.ChunkOffset{}, } - track.TrackAtom = &atom.Track{ + stream.trackAtom = &atom.Track{ Header: &atom.TrackHeader{ - TrackId: len(self.Tracks)+1, - Flags: 0x0003, // Track enabled | Track in movie - Duration: 0, // fill later - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + TrackId: len(self.streams) + 1, + Flags: 0x0003, // Track enabled | Track in movie + Duration: 0, // fill later + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, }, Media: &atom.Media{ Header: &atom.MediaHeader{ TimeScale: 0, // fill later - Duration: 0, // fill later - Language: 21956, + Duration: 0, // fill later + Language: 21956, }, Info: &atom.MediaInfo{ - Sample: track.sample, + Sample: stream.sample, Data: &atom.DataInfo{ Refer: &atom.DataRefer{ Url: &atom.DataReferUrl{ @@ -63,57 +60,86 @@ func (self *Muxer) newTrack() *Track { }, } - track.writeMdat = self.writeMdat - self.Tracks = append(self.Tracks, track) + stream.writeMdat = self.writeMdat + self.streams = append(self.streams, stream) - return track + return stream } -func (self *Muxer) AddAACTrack() (track *Track) { - track = self.newTrack() - self.TrackAAC = track - track.Type = AAC - track.sample.SampleDesc.Mp4aDesc = &atom.Mp4aDesc{ - DataRefIdx: 1, - NumberOfChannels: 0, // fill later - SampleSize: 0, // fill later - SampleRate: 0, // fill later - Conf: &atom.ElemStreamDesc{}, +func (self *Stream) fillTrackAtom() (err error) { + if self.sampleIndex > 0 { + self.sttsEntry.Count++ } - track.TrackAtom.Header.Volume = atom.IntToFixed(1) - track.TrackAtom.Header.AlternateGroup = 1 - track.TrackAtom.Media.Handler = &atom.HandlerRefer{ - SubType: "soun", - Name: "Sound Handler", - } - track.TrackAtom.Media.Info.Sound = &atom.SoundMediaInfo{} - return -} -func (self *Muxer) AddH264Track() (track *Track) { - track = self.newTrack() - self.TrackH264 = track - track.Type = H264 - track.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ - DataRefIdx: 1, - HorizontalResolution: 72, - VorizontalResolution: 72, - Width: 0, // fill later - Height: 0, // fill later - FrameCount: 1, - Depth: 24, - ColorTableId: -1, - Conf: &atom.Avc1Conf{}, - } - track.sample.SyncSample = &atom.SyncSample{} - track.TrackAtom.Media.Handler = &atom.HandlerRefer{ - SubType: "vide", - Name: "Video Media Handler", - } - track.sample.CompositionOffset = &atom.CompositionOffset{} - track.TrackAtom.Media.Info.Video = &atom.VideoMediaInfo{ - Flags: 0x000001, + self.trackAtom.Media.Header.TimeScale = int(self.TimeScale()) + self.trackAtom.Media.Header.Duration = int(self.lastDts) + + if self.Type() == av.H264 { + self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( + self.sps, + self.pps, + ) + if err != nil { + return + } + var info *atom.H264SPSInfo + if info, err = atom.ParseH264SPS(self.sps[1:]); err != nil { + return + } + self.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: int(info.Width), + Height: int(info.Height), + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + Conf: &atom.Avc1Conf{}, + } + self.sample.SyncSample = &atom.SyncSample{} + self.trackAtom.Media.Handler = &atom.HandlerRefer{ + SubType: "vide", + Name: "Video Media Handler", + } + self.sample.CompositionOffset = &atom.CompositionOffset{} + self.trackAtom.Media.Info.Video = &atom.VideoMediaInfo{ + Flags: 0x000001, + } + self.trackAtom.Header.TrackWidth = atom.IntToFixed(int(info.Width)) + self.trackAtom.Header.TrackHeight = atom.IntToFixed(int(info.Height)) + + } else if self.Type() == av.AAC { + if !self.mpeg4AudioConfig.IsValid() { + err = fmt.Errorf("invalie MPEG4AudioConfig") + return + } + buf := &bytes.Buffer{} + config := self.mpeg4AudioConfig.Complete() + if err = isom.WriteElemStreamDescAAC(buf, config, uint(self.trackAtom.Header.TrackId)); err != nil { + return + } + self.sample.SampleDesc.Mp4aDesc = &atom.Mp4aDesc{ + DataRefIdx: 1, + NumberOfChannels: config.ChannelCount, + SampleSize: config.ChannelCount * 8, + SampleRate: atom.IntToFixed(config.SampleRate), + Conf: &atom.ElemStreamDesc{ + Data: buf.Bytes(), + }, + } + self.trackAtom.Header.Volume = atom.IntToFixed(1) + self.trackAtom.Header.AlternateGroup = 1 + self.trackAtom.Media.Handler = &atom.HandlerRefer{ + SubType: "soun", + Name: "Sound Handler", + } + self.trackAtom.Media.Info.Sound = &atom.SoundMediaInfo{} + + } else { + err = fmt.Errorf("please specify stream type") } + return } @@ -132,22 +158,12 @@ func (self *Muxer) WriteHeader() (err error) { return } -func (self *Track) SetH264PPSAndSPS(pps, sps []byte) { - self.pps, self.sps = pps, sps -} +func (self *Muxer) WriteSample(pkt av.Packet) (err error) { + pts, dts, isKeyFrame, frame := pkt.Pts, pkt.Dts, pkt.IsKeyFrame, pkt.Data + stream := self.streams[pkt.StreamIdx] -func (self *Track) SetMPEG4AudioConfig(config isom.MPEG4AudioConfig) { - self.mpeg4AudioConfig = config -} - -func (self *Track) SetTimeScale(timeScale int64) { - self.TrackAtom.Media.Header.TimeScale = int(timeScale) - return -} - -func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, frame []byte) (err error) { - if self.Type == AAC && isom.IsADTSFrame(frame) { - config := self.mpeg4AudioConfig.Complete() + if stream.Type() == av.AAC && isom.IsADTSFrame(frame) { + config := stream.mpeg4AudioConfig.Complete() if config.SampleRate == 0 { err = fmt.Errorf("invalid sample rate") return @@ -159,19 +175,20 @@ func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, frame []by if _, payload, samples, framelen, err = isom.ReadADTSFrame(frame); err != nil { return } - delta := int64(samples)*self.TimeScale()/int64(config.SampleRate) + delta := int64(samples) * stream.TimeScale() / int64(config.SampleRate) pts += delta dts += delta frame = frame[framelen:] - if self.writeSample(pts, dts, isKeyFrame, payload); err != nil { + if stream.writeSample(pts, dts, isKeyFrame, payload); err != nil { return } } } - return self.writeSample(pts, dts, isKeyFrame, frame) + + return stream.writeSample(pts, dts, isKeyFrame, frame) } -func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { +func (self *Stream) writeSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { var filePos int64 sampleSize := len(data) if filePos, err = self.writeMdat(data); err != nil { @@ -187,7 +204,7 @@ func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byt err = fmt.Errorf("dts must be incremental") return } - duration := int(dts-self.lastDts) + duration := int(dts - self.lastDts) if self.sttsEntry == nil || duration != self.sttsEntry.Duration { self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, atom.TimeToSampleEntry{Duration: duration}) self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1] @@ -200,7 +217,7 @@ func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byt err = fmt.Errorf("pts must greater than dts") return } - offset := int(pts-dts) + offset := int(pts - dts) if self.cttsEntry == nil || offset != self.cttsEntry.Offset { table := self.sample.CompositionOffset table.Entries = append(table.Entries, atom.CompositionOffsetEntry{Offset: offset}) @@ -217,71 +234,30 @@ func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byt return } -func (self *Track) fillTrackAtom() (err error) { - if self.sampleIndex > 0 { - self.sttsEntry.Count++ - } - - if self.Type == H264 { - self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( - self.sps, - self.pps, - ) - if err != nil { - return - } - var info *atom.H264SPSInfo - if info, err = atom.ParseH264SPS(self.sps[1:]); err != nil { - return - } - self.sample.SampleDesc.Avc1Desc.Width = int(info.Width) - self.sample.SampleDesc.Avc1Desc.Height = int(info.Height) - self.TrackAtom.Header.TrackWidth = atom.IntToFixed(int(info.Width)) - self.TrackAtom.Header.TrackHeight = atom.IntToFixed(int(info.Height)) - self.TrackAtom.Media.Header.Duration = int(self.lastDts) - } else if self.Type == AAC { - if !self.mpeg4AudioConfig.IsValid() { - err = fmt.Errorf("invalie MPEG4AudioConfig") - return - } - buf := &bytes.Buffer{} - config := self.mpeg4AudioConfig.Complete() - if err = isom.WriteElemStreamDescAAC(buf, config, uint(self.TrackAtom.Header.TrackId)); err != nil { - return - } - self.sample.SampleDesc.Mp4aDesc.Conf.Data = buf.Bytes() - self.sample.SampleDesc.Mp4aDesc.NumberOfChannels = config.ChannelCount - self.sample.SampleDesc.Mp4aDesc.SampleSize = config.ChannelCount*8 - self.sample.SampleDesc.Mp4aDesc.SampleRate = atom.IntToFixed(config.SampleRate) - self.TrackAtom.Media.Header.Duration = int(self.lastDts) - } - return -} - func (self *Muxer) WriteTrailer() (err error) { moov := &atom.Movie{} moov.Header = &atom.MovieHeader{ - PreferredRate: atom.IntToFixed(1), + PreferredRate: atom.IntToFixed(1), PreferredVolume: atom.IntToFixed(1), - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - NextTrackId: 2, + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + NextTrackId: 2, } maxDur := float64(0) timeScale := 10000 - for _, track := range(self.Tracks) { - if err = track.fillTrackAtom(); err != nil { + for _, stream := range self.streams { + if err = stream.fillTrackAtom(); err != nil { return } - dur := track.Duration() - track.TrackAtom.Header.Duration = int(float64(timeScale)*dur) + dur := stream.Duration() + stream.trackAtom.Header.Duration = int(float64(timeScale) * dur) if dur > maxDur { maxDur = dur } - moov.Tracks = append(moov.Tracks, track.TrackAtom) + moov.Tracks = append(moov.Tracks, stream.trackAtom) } moov.Header.TimeScale = timeScale - moov.Header.Duration = int(float64(timeScale)*maxDur) + moov.Header.Duration = int(float64(timeScale) * maxDur) if err = self.mdatWriter.Close(); err != nil { return @@ -292,226 +268,3 @@ func (self *Muxer) WriteTrailer() (err error) { return } - -/* -type SimpleH264Writer struct { - W io.WriteSeeker - - TimeScale int - sps []byte - pps []byte - - Width int - Height int - - duration int - - sample *atom.SampleTable - sampleToChunk *atom.SampleToChunkEntry - sampleIdx int - timeToSample *atom.TimeToSampleEntry - - mdatWriter *atom.Writer -} - -func (self *SimpleH264Writer) prepare() (err error) { - if self.mdatWriter, err = atom.WriteAtomHeader(self.W, "mdat"); err != nil { - return - } - - if len(self.sps) == 0 { - err = fmt.Errorf("invalid sps") - return - } - - if len(self.pps) == 0 { - err = fmt.Errorf("invalid pps") - return - } - - if self.Width == 0 || self.Height == 0 { - var info *atom.H264spsInfo - if info, err = atom.ParseH264sps(self.sps[1:]); err != nil { - return - } - self.Width = int(info.Width) - self.Height = int(info.Height) - } - - self.sampleIdx = 1 - - self.sample = &atom.SampleTable{ - SampleDesc: &atom.SampleDesc{ - Avc1Desc: &atom.Avc1Desc{ - DataRefIdx: 1, - HorizontalResolution: 72, - VorizontalResolution: 72, - Width: self.Width, - Height: self.Height, - FrameCount: 1, - Depth: 24, - ColorTableId: -1, - Conf: &atom.Avc1Conf{}, - }, - }, - TimeToSample: &atom.TimeToSample{}, - SampleToChunk: &atom.SampleToChunk{ - Entries: []atom.SampleToChunkEntry{ - { - FirstChunk: 1, - SampleDescId: 1, - }, - }, - }, - SampleSize: &atom.SampleSize{}, - ChunkOffset: &atom.ChunkOffset{ - Entries: []int{8}, - }, - SyncSample: &atom.SyncSample{}, - } - self.sampleToChunk = &self.sample.SampleToChunk.Entries[0] - - return -} - -func (self *SimpleH264Writer) WriteSample(sync bool, duration int, data []byte) (err error) { - return self.writeSample(false, sync, duration, data) -} - -func (self *SimpleH264Writer) WriteNALU(sync bool, duration int, data []byte) (err error) { - return self.writeSample(true, sync, duration, data) -} - -func splitNALUByStartCode(data []byte) (out [][]byte) { - last := 0 - for i := 0; i < len(data)-3; { - if data[i] == 0 && data[i+1] == 0 && data[i+2] == 1 { - out = append(out, data[last:i]) - i += 3 - last = i - } else { - i++ - } - } - out = append(out, data[last:]) - return -} - -func (self *SimpleH264Writer) writeSample(isNALU, sync bool, duration int, data []byte) (err error) { - if self.mdatWriter == nil { - if err = self.prepare(); err != nil { - return - } - } - - var sampleSize int - - if isNALU { - if sampleSize, err = atom.WriteSampleByNALU(self.mdatWriter, data); err != nil { - return - } - } else { - sampleSize = len(data) - if _, err = self.mdatWriter.Write(data); err != nil { - return - } - } - - if sync { - self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, self.sampleIdx) - } - - if self.timeToSample != nil && duration != self.timeToSample.Duration { - self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, *self.timeToSample) - self.timeToSample = nil - } - if self.timeToSample == nil { - self.timeToSample = &atom.TimeToSampleEntry{ - Duration: duration, - } - } - - self.duration += duration - self.sampleIdx++ - self.timeToSample.Count++ - self.sampleToChunk.SamplesPerChunk++ - self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, sampleSize) - - return -} - -func (self *SimpleH264Writer) Finish() (err error) { - self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( - self.sps, - self.pps, - ) - if err != nil { - return - } - - if self.timeToSample != nil { - self.sample.TimeToSample.Entries = append( - self.sample.TimeToSample.Entries, - *self.timeToSample, - ) - } - - if err = self.mdatWriter.Close(); err != nil { - return - } - - moov := &atom.Movie{} - moov.Header = &atom.MovieHeader{ - TimeScale: self.TimeScale, - Duration: self.duration, - PreferredRate: atom.IntToFixed(1), - PreferredVolume: atom.IntToFixed(1), - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - NextTrackId: 2, - } - - track := &atom.Track{ - Header: &atom.TrackHeader{ - TrackId: 1, - Flags: 0x0003, // Track enabled | Track in movie - Duration: self.duration, - Volume: atom.IntToFixed(1), - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - TrackWidth: atom.IntToFixed(self.Width), - TrackHeight: atom.IntToFixed(self.Height), - }, - - Media: &atom.Media{ - Header: &atom.MediaHeader{ - TimeScale: self.TimeScale, - Duration: self.duration, - }, - Info: &atom.MediaInfo{ - Video: &atom.VideoMediaInfo{ - Flags: 0x000001, - }, - Sample: self.sample, - Data: &atom.DataInfo{ - Refer: &atom.DataRefer{ - Url: &atom.DataReferUrl{ - Flags: 0x000001, // Self reference - }, - }, - }, - }, - Handler: &atom.HandlerRefer{ - SubType: "vide", - Name: "Video Media Handler", - }, - }, - } - moov.Tracks = append(moov.Tracks, track) - - if err = atom.WriteMovie(self.W, moov); err != nil { - return - } - - return -} -*/ - diff --git a/track.go b/track.go deleted file mode 100644 index 0d13ef2..0000000 --- a/track.go +++ /dev/null @@ -1,47 +0,0 @@ - -package mp4 - -import ( - "github.com/nareix/mp4/atom" - "github.com/nareix/mp4/isom" - "io" -) - -const ( - H264 = 1 - AAC = 2 -) - -type Track struct { - Type int - TrackAtom *atom.Track - r io.ReadSeeker - - sps []byte - pps []byte - - mpeg4AudioConfig isom.MPEG4AudioConfig - - sample *atom.SampleTable - sampleIndex int - - sampleOffsetInChunk int64 - syncSampleIndex int - - dts int64 - sttsEntryIndex int - sampleIndexInSttsEntry int - - cttsEntryIndex int - sampleIndexInCttsEntry int - - chunkGroupIndex int - chunkIndex int - sampleIndexInChunk int - - sttsEntry *atom.TimeToSampleEntry - cttsEntry *atom.CompositionOffsetEntry - writeMdat func ([]byte) (int64,error) - lastDts int64 -} - From c6b37ad0a5b7ea1a4b394ede1f1f6a085222b7bf Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 19 Apr 2016 11:53:33 +0800 Subject: [PATCH 62/93] add avcc size prefix when write raw h264 NALU Conflicts: muxer.go track.go --- muxer.go | 66 +++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/muxer.go b/muxer.go index bb144f3..2d9e2e5 100644 --- a/muxer.go +++ b/muxer.go @@ -6,6 +6,7 @@ import ( "github.com/nareix/av" "github.com/nareix/mp4/atom" "github.com/nareix/mp4/isom" + "github.com/nareix/codec/h264parser" "io" ) @@ -60,7 +61,7 @@ func (self *Muxer) NewStream() av.Stream { }, } - stream.writeMdat = self.writeMdat + stream.muxer = self self.streams = append(self.streams, stream) return stream @@ -75,27 +76,17 @@ func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Media.Header.Duration = int(self.lastDts) if self.Type() == av.H264 { - self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( - self.sps, - self.pps, - ) - if err != nil { - return - } - var info *atom.H264SPSInfo - if info, err = atom.ParseH264SPS(self.sps[1:]); err != nil { - return - } + width, height := self.Width(), self.Height() self.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ DataRefIdx: 1, HorizontalResolution: 72, VorizontalResolution: 72, - Width: int(info.Width), - Height: int(info.Height), + Width: int(width), + Height: int(height), FrameCount: 1, Depth: 24, ColorTableId: -1, - Conf: &atom.Avc1Conf{}, + Conf: &atom.Avc1Conf{Data: self.CodecData()}, } self.sample.SyncSample = &atom.SyncSample{} self.trackAtom.Media.Handler = &atom.HandlerRefer{ @@ -106,24 +97,19 @@ func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Media.Info.Video = &atom.VideoMediaInfo{ Flags: 0x000001, } - self.trackAtom.Header.TrackWidth = atom.IntToFixed(int(info.Width)) - self.trackAtom.Header.TrackHeight = atom.IntToFixed(int(info.Height)) + self.trackAtom.Header.TrackWidth = atom.IntToFixed(int(width)) + self.trackAtom.Header.TrackHeight = atom.IntToFixed(int(height)) } else if self.Type() == av.AAC { - if !self.mpeg4AudioConfig.IsValid() { - err = fmt.Errorf("invalie MPEG4AudioConfig") - return - } buf := &bytes.Buffer{} - config := self.mpeg4AudioConfig.Complete() - if err = isom.WriteElemStreamDescAAC(buf, config, uint(self.trackAtom.Header.TrackId)); err != nil { + if err = isom.WriteElemStreamDesc(buf, self.CodecData(), uint(self.trackAtom.Header.TrackId)); err != nil { return } self.sample.SampleDesc.Mp4aDesc = &atom.Mp4aDesc{ DataRefIdx: 1, - NumberOfChannels: config.ChannelCount, - SampleSize: config.ChannelCount * 8, - SampleRate: atom.IntToFixed(config.SampleRate), + NumberOfChannels: self.ChannelCount(), + SampleSize: self.ChannelCount() * 8, + SampleRate: atom.IntToFixed(self.SampleRate()), Conf: &atom.ElemStreamDesc{ Data: buf.Bytes(), }, @@ -163,11 +149,6 @@ func (self *Muxer) WriteSample(pkt av.Packet) (err error) { stream := self.streams[pkt.StreamIdx] if stream.Type() == av.AAC && isom.IsADTSFrame(frame) { - config := stream.mpeg4AudioConfig.Complete() - if config.SampleRate == 0 { - err = fmt.Errorf("invalid sample rate") - return - } for len(frame) > 0 { var payload []byte var samples int @@ -175,7 +156,7 @@ func (self *Muxer) WriteSample(pkt av.Packet) (err error) { if _, payload, samples, framelen, err = isom.ReadADTSFrame(frame); err != nil { return } - delta := int64(samples) * stream.TimeScale() / int64(config.SampleRate) + delta := int64(samples) * stream.TimeScale() / int64(stream.SampleRate()) pts += delta dts += delta frame = frame[framelen:] @@ -190,11 +171,28 @@ func (self *Muxer) WriteSample(pkt av.Packet) (err error) { func (self *Stream) writeSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { var filePos int64 - sampleSize := len(data) - if filePos, err = self.writeMdat(data); err != nil { + var sampleSize int + + if filePos, err = self.muxer.mdatWriter.Seek(0, 1); err != nil { return } + if self.Type() == av.H264 { + nalus, _ := h264parser.SplitNALUs(data) + h264parser.WalkNALUsAVCC(nalus, func(b []byte) { + sampleSize += len(b) + _, err = self.muxer.mdatWriter.Write(b) + }) + if err != nil { + return + } + } else { + sampleSize = len(data) + if _, err = self.muxer.mdatWriter.Write(data); err != nil { + return + } + } + if isKeyFrame && self.sample.SyncSample != nil { self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, self.sampleIndex+1) } From 2ffa926532acdd24cc5d47cab3e6f6443a38a3cb Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 19 Apr 2016 18:45:32 +0800 Subject: [PATCH 63/93] many modifications to change to new api --- atom/genStruct.js | 2 +- atom/otherStruct.go | 347 +------------------------------------------- atom/struct.go | 25 ++-- atom/utils.go | 10 +- demuxer.go | 78 ++++++---- isom/isom.go | 312 +-------------------------------------- muxer.go | 7 +- stream.go | 41 ++++++ 8 files changed, 117 insertions(+), 705 deletions(-) create mode 100644 stream.go diff --git a/atom/genStruct.js b/atom/genStruct.js index 6a2122f..53c8b38 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -259,7 +259,7 @@ var atoms = { avc1Conf: { cc4: 'avcC', fields: [ - ['record', 'AVCDecoderConfRecord'], + ['data', '[]byte'], ], }, diff --git a/atom/otherStruct.go b/atom/otherStruct.go index 2c5d154..0652812 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -1,357 +1,12 @@ package atom import ( - "bytes" + _ "bytes" "fmt" "github.com/nareix/bits" "io" ) -type H264SPSInfo struct { - ProfileIdc uint - LevelIdc uint - - MbWidth uint - MbHeight uint - - CropLeft uint - CropRight uint - CropTop uint - CropBottom uint - - Width uint - Height uint -} - -func ParseH264SPS(data []byte) (res *H264SPSInfo, err error) { - r := &bits.GolombBitReader{ - R: bytes.NewReader(data), - } - - self := &H264SPSInfo{} - - if self.ProfileIdc, err = r.ReadBits(8); err != nil { - return - } - - // constraint_set0_flag-constraint_set6_flag,reserved_zero_2bits - if _, err = r.ReadBits(8); err != nil { - return - } - - // level_idc - if self.LevelIdc, err = r.ReadBits(8); err != nil { - return - } - - // seq_parameter_set_id - if _, err = r.ReadExponentialGolombCode(); err != nil { - return - } - - if self.ProfileIdc == 100 || self.ProfileIdc == 110 || - self.ProfileIdc == 122 || self.ProfileIdc == 244 || - self.ProfileIdc == 44 || self.ProfileIdc == 83 || - self.ProfileIdc == 86 || self.ProfileIdc == 118 { - - var chroma_format_idc uint - if chroma_format_idc, err = r.ReadExponentialGolombCode(); err != nil { - return - } - - if chroma_format_idc == 3 { - // residual_colour_transform_flag - if _, err = r.ReadBit(); err != nil { - return - } - } - - // bit_depth_luma_minus8 - if _, err = r.ReadExponentialGolombCode(); err != nil { - return - } - // bit_depth_chroma_minus8 - if _, err = r.ReadExponentialGolombCode(); err != nil { - return - } - // qpprime_y_zero_transform_bypass_flag - if _, err = r.ReadBit(); err != nil { - return - } - - var seq_scaling_matrix_present_flag uint - if seq_scaling_matrix_present_flag, err = r.ReadBit(); err != nil { - return - } - - if seq_scaling_matrix_present_flag != 0 { - for i := 0; i < 8; i++ { - var seq_scaling_list_present_flag uint - if seq_scaling_list_present_flag, err = r.ReadBit(); err != nil { - return - } - if seq_scaling_list_present_flag != 0 { - var sizeOfScalingList uint - if i < 6 { - sizeOfScalingList = 16 - } else { - sizeOfScalingList = 64 - } - lastScale := uint(8) - nextScale := uint(8) - for j := uint(0); j < sizeOfScalingList; j++ { - if nextScale != 0 { - var delta_scale uint - if delta_scale, err = r.ReadSE(); err != nil { - return - } - nextScale = (lastScale + delta_scale + 256) % 256 - } - if nextScale != 0 { - lastScale = nextScale - } - } - } - } - } - } - - // log2_max_frame_num_minus4 - if _, err = r.ReadExponentialGolombCode(); err != nil { - return - } - - var pic_order_cnt_type uint - if pic_order_cnt_type, err = r.ReadExponentialGolombCode(); err != nil { - return - } - if pic_order_cnt_type == 0 { - // log2_max_pic_order_cnt_lsb_minus4 - if _, err = r.ReadExponentialGolombCode(); err != nil { - return - } - } else if pic_order_cnt_type == 1 { - // delta_pic_order_always_zero_flag - if _, err = r.ReadBit(); err != nil { - return - } - // offset_for_non_ref_pic - if _, err = r.ReadSE(); err != nil { - return - } - // offset_for_top_to_bottom_field - if _, err = r.ReadSE(); err != nil { - return - } - var num_ref_frames_in_pic_order_cnt_cycle uint - if num_ref_frames_in_pic_order_cnt_cycle, err = r.ReadExponentialGolombCode(); err != nil { - return - } - for i := uint(0); i < num_ref_frames_in_pic_order_cnt_cycle; i++ { - if _, err = r.ReadSE(); err != nil { - return - } - } - } - - // max_num_ref_frames - if _, err = r.ReadExponentialGolombCode(); err != nil { - return - } - - // gaps_in_frame_num_value_allowed_flag - if _, err = r.ReadBit(); err != nil { - return - } - - if self.MbWidth, err = r.ReadExponentialGolombCode(); err != nil { - return - } - self.MbWidth++ - - if self.MbHeight, err = r.ReadExponentialGolombCode(); err != nil { - return - } - self.MbHeight++ - - var frame_mbs_only_flag uint - if frame_mbs_only_flag, err = r.ReadBit(); err != nil { - return - } - if frame_mbs_only_flag == 0 { - // mb_adaptive_frame_field_flag - if _, err = r.ReadBit(); err != nil { - return - } - } - - // direct_8x8_inference_flag - if _, err = r.ReadBit(); err != nil { - return - } - - var frame_cropping_flag uint - if frame_cropping_flag, err = r.ReadBit(); err != nil { - return - } - if frame_cropping_flag != 0 { - if self.CropLeft, err = r.ReadExponentialGolombCode(); err != nil { - return - } - if self.CropRight, err = r.ReadExponentialGolombCode(); err != nil { - return - } - if self.CropTop, err = r.ReadExponentialGolombCode(); err != nil { - return - } - if self.CropBottom, err = r.ReadExponentialGolombCode(); err != nil { - return - } - } - - self.Width = (self.MbWidth * 16) - self.CropLeft*2 - self.CropRight*2 - self.Height = ((2 - frame_mbs_only_flag) * self.MbHeight * 16) - self.CropTop*2 - self.CropBottom*2 - - res = self - return -} - -type AVCDecoderConfRecord struct { - AVCProfileIndication int - ProfileCompatibility int - AVCLevelIndication int - LengthSizeMinusOne int - SPS [][]byte - PPS [][]byte -} - -func CreateAVCDecoderConfRecord( - SPS []byte, - PPS []byte, -) (self AVCDecoderConfRecord, err error) { - if len(SPS) < 4 { - err = fmt.Errorf("invalid SPS data") - return - } - self.AVCProfileIndication = int(SPS[1]) - self.ProfileCompatibility = int(SPS[2]) - self.AVCLevelIndication = int(SPS[3]) - self.SPS = [][]byte{SPS} - self.PPS = [][]byte{PPS} - self.LengthSizeMinusOne = 3 - return -} - -func WriteAVCDecoderConfRecord(w io.Writer, self AVCDecoderConfRecord) (err error) { - if err = WriteInt(w, 1, 1); err != nil { - return - } - if err = WriteInt(w, self.AVCProfileIndication, 1); err != nil { - return - } - if err = WriteInt(w, self.ProfileCompatibility, 1); err != nil { - return - } - if err = WriteInt(w, self.AVCLevelIndication, 1); err != nil { - return - } - if err = WriteInt(w, self.LengthSizeMinusOne|0xfc, 1); err != nil { - return - } - - if err = WriteInt(w, len(self.SPS)|0xe0, 1); err != nil { - return - } - for _, data := range self.SPS { - if err = WriteInt(w, len(data), 2); err != nil { - return - } - if err = WriteBytes(w, data, len(data)); err != nil { - return - } - } - - if err = WriteInt(w, len(self.PPS), 1); err != nil { - return - } - for _, data := range self.PPS { - if err = WriteInt(w, len(data), 2); err != nil { - return - } - if err = WriteBytes(w, data, len(data)); err != nil { - return - } - } - - return -} - -func WalkAVCDecoderConfRecord(w Walker, self AVCDecoderConfRecord) { -} - -func ReadAVCDecoderConfRecord(r *io.LimitedReader) (self AVCDecoderConfRecord, err error) { - if _, err = ReadDummy(r, 1); err != nil { - return - } - if self.AVCProfileIndication, err = ReadInt(r, 1); err != nil { - return - } - if self.ProfileCompatibility, err = ReadInt(r, 1); err != nil { - return - } - if self.AVCLevelIndication, err = ReadInt(r, 1); err != nil { - return - } - if self.LengthSizeMinusOne, err = ReadInt(r, 1); err != nil { - return - } - self.LengthSizeMinusOne &= 0x03 - - var n, length int - var data []byte - - if n, err = ReadInt(r, 1); err != nil { - return - } - n &= 0x1f - for i := 0; i < n; i++ { - if length, err = ReadInt(r, 2); err != nil { - return - } - if data, err = ReadBytes(r, length); err != nil { - return - } - self.SPS = append(self.SPS, data) - } - - if n, err = ReadInt(r, 1); err != nil { - return - } - for i := 0; i < n; i++ { - if length, err = ReadInt(r, 2); err != nil { - return - } - if data, err = ReadBytes(r, length); err != nil { - return - } - self.PPS = append(self.PPS, data) - } - - return -} - -func WriteSampleByNALU(w io.Writer, nalu []byte) (size int, err error) { - if err = WriteInt(w, len(nalu), 4); err != nil { - return - } - size += 4 - if _, err = w.Write(nalu); err != nil { - return - } - size += len(nalu) - return -} - const ( TFHD_BASE_DATA_OFFSET = 0x01 TFHD_STSD_ID = 0x02 diff --git a/atom/struct.go b/atom/struct.go index 985840a..bc49400 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -565,11 +565,11 @@ func WalkTrackHeader(w Walker, self *TrackHeader) { } type HandlerRefer struct { - Version int - Flags int - CodecType string - SubType string - Name string + Version int + Flags int + Type string + SubType string + Name string } func ReadHandlerRefer(r *io.LimitedReader) (res *HandlerRefer, err error) { @@ -581,7 +581,7 @@ func ReadHandlerRefer(r *io.LimitedReader) (res *HandlerRefer, err error) { if self.Flags, err = ReadInt(r, 3); err != nil { return } - if self.CodecType, err = ReadString(r, 4); err != nil { + if self.Type, err = ReadString(r, 4); err != nil { return } if self.SubType, err = ReadString(r, 4); err != nil { @@ -606,7 +606,7 @@ func WriteHandlerRefer(w io.WriteSeeker, self *HandlerRefer) (err error) { if err = WriteInt(w, self.Flags, 3); err != nil { return } - if err = WriteString(w, self.CodecType, 4); err != nil { + if err = WriteString(w, self.Type, 4); err != nil { return } if err = WriteString(w, self.SubType, 4); err != nil { @@ -628,7 +628,7 @@ func WalkHandlerRefer(w Walker, self *HandlerRefer) { w.Name("Flags") w.Int(self.Flags) w.Name("Type") - w.String(self.CodecType) + w.String(self.Type) w.Name("SubType") w.String(self.SubType) w.Name("Name") @@ -1874,13 +1874,13 @@ func WalkAvc1Desc(w Walker, self *Avc1Desc) { } type Avc1Conf struct { - Record AVCDecoderConfRecord + Data []byte } func ReadAvc1Conf(r *io.LimitedReader) (res *Avc1Conf, err error) { self := &Avc1Conf{} - if self.Record, err = ReadAVCDecoderConfRecord(r); err != nil { + if self.Data, err = ReadBytes(r, int(r.N)); err != nil { return } res = self @@ -1893,7 +1893,7 @@ func WriteAvc1Conf(w io.WriteSeeker, self *Avc1Conf) (err error) { return } w = aw - if err = WriteAVCDecoderConfRecord(w, self.Record); err != nil { + if err = WriteBytes(w, self.Data, len(self.Data)); err != nil { return } if err = aw.Close(); err != nil { @@ -1904,7 +1904,8 @@ func WriteAvc1Conf(w io.WriteSeeker, self *Avc1Conf) (err error) { func WalkAvc1Conf(w Walker, self *Avc1Conf) { w.StartStruct("Avc1Conf") - WalkAVCDecoderConfRecord(w, self.Record) + w.Name("Data") + w.Bytes(self.Data) w.EndStruct() return } diff --git a/atom/utils.go b/atom/utils.go index 3f16f8b..5c24473 100644 --- a/atom/utils.go +++ b/atom/utils.go @@ -1,14 +1,12 @@ package atom -func GetAVCDecoderConfRecordByTrack(stream *Track) (record *AVCDecoderConfRecord) { +func GetAvc1ConfByTrack(stream *Track) (avc1 *Avc1Conf) { if media := stream.Media; media != nil { if info := media.Info; info != nil { if sample := info.Sample; sample != nil { if desc := sample.SampleDesc; desc != nil { if avc1 := desc.Avc1Desc; avc1 != nil { - if conf := avc1.Conf; conf != nil { - return &conf.Record - } + return avc1.Conf } } } @@ -22,9 +20,7 @@ func GetMp4aDescByTrack(stream *Track) (mp4a *Mp4aDesc) { if info := media.Info; info != nil { if sample := info.Sample; sample != nil { if desc := sample.SampleDesc; desc != nil { - if mp4a = desc.Mp4aDesc; mp4a != nil { - return - } + return desc.Mp4aDesc } } } diff --git a/demuxer.go b/demuxer.go index 315c798..82f0b48 100644 --- a/demuxer.go +++ b/demuxer.go @@ -16,6 +16,13 @@ type Demuxer struct { movieAtom *atom.Movie } +func (self *Demuxer) Streams() (streams []av.Stream) { + for _, stream := range(self.streams) { + streams = append(streams, stream) + } + return +} + func (self *Demuxer) ReadHeader() (err error) { var N int64 var moov *atom.Movie @@ -54,10 +61,11 @@ func (self *Demuxer) ReadHeader() (err error) { self.movieAtom = moov self.streams = []*Stream{} - for _, atrack := range moov.Tracks { + for i, atrack := range moov.Tracks { stream := &Stream{ trackAtom: atrack, r: self.R, + idx: i, } if atrack.Media != nil && atrack.Media.Info != nil && atrack.Media.Info.Sample != nil { stream.sample = atrack.Media.Info.Sample @@ -66,21 +74,25 @@ func (self *Demuxer) ReadHeader() (err error) { err = fmt.Errorf("sample table not found") return } - if record := atom.GetAVCDecoderConfRecordByTrack(atrack); record != nil { - if len(record.PPS) > 0 { - stream.pps = record.PPS[0] - } - if len(record.SPS) > 0 { - stream.sps = record.SPS[0] - } + + if avc1 := atom.GetAvc1ConfByTrack(atrack); avc1 != nil { stream.SetType(av.H264) - self.streams = append(self.streams, stream) - } else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil && mp4a.Conf != nil { - if config, err := isom.ReadElemStreamDescAAC(bytes.NewReader(mp4a.Conf.Data)); err == nil { - stream.mpeg4AudioConfig = config.Complete() - stream.SetType(av.AAC) - self.streams = append(self.streams, stream) + if err = stream.SetCodecData(avc1.Data); err != nil { + return } + self.streams = append(self.streams, stream) + + } else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil && mp4a.Conf != nil { + stream.SetType(av.AAC) + var config []byte + if config, err = isom.ReadElemStreamDesc(bytes.NewReader(mp4a.Conf.Data)); err != nil { + return + } + if err = stream.SetCodecData(config); err != nil { + return + } + self.streams = append(self.streams, stream) + } } @@ -250,7 +262,7 @@ func (self *Stream) incSampleIndex() { self.sampleIndex++ } -func (self *Stream) SampleCount() int { +func (self *Stream) sampleCount() int { if self.sample.SampleSize.SampleSize == 0 { chunkGroupIndex := 0 count := 0 @@ -269,6 +281,23 @@ func (self *Stream) SampleCount() int { } func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { + var choose *Stream + for _, stream := range(self.streams) { + if choose == nil || stream.dts < choose.dts { + choose = stream + } + } + pkt.StreamIdx = choose.idx + pkt.Pts, pkt.Dts, pkt.IsKeyFrame, pkt.Data, err = choose.readSample() + return +} + +func (self *Demuxer) SeekToTime(time float64) (err error) { + for _, stream := range(self.streams) { + if err = stream.seekToTime(time); err != nil { + return + } + } return } @@ -314,28 +343,20 @@ func (self *Stream) readSample() (pts int64, dts int64, isKeyFrame bool, data [] return } -func (self *Stream) Duration() float64 { +func (self *Stream) duration() float64 { total := int64(0) for _, entry := range self.sample.TimeToSample.Entries { total += int64(entry.Duration * entry.Count) } - return float64(total) / float64(self.trackAtom.Media.Header.TimeScale) + return float64(total) / float64(self.TimeScale()) } -func (self *Stream) CurSampleIndex() int { - return self.sampleIndex -} - -func (self *Stream) SeekToTime(time float64) error { - index := self.TimeToSampleIndex(time) +func (self *Stream) seekToTime(time float64) error { + index := self.timeToSampleIndex(time) return self.setSampleIndex(index) } -func (self *Stream) SeekToSampleIndex(index int) error { - return self.setSampleIndex(index) -} - -func (self *Stream) TimeToSampleIndex(time float64) int { +func (self *Stream) timeToSampleIndex(time float64) int { targetTs := self.TimeToTs(time) targetIndex := 0 @@ -374,3 +395,4 @@ func (self *Stream) TimeToSampleIndex(time float64) int { return targetIndex } + diff --git a/isom/isom.go b/isom/isom.go index f81e3af..c08d808 100644 --- a/isom/isom.go +++ b/isom/isom.go @@ -9,6 +9,7 @@ import ( ) // copied from libavformat/isom.h + const ( MP4ESDescrTag = 3 MP4DecConfigDescrTag = 4 @@ -18,295 +19,6 @@ const ( var debugReader = false var debugWriter = false -// copied from libavcodec/mpeg4audio.h -const ( - AOT_AAC_MAIN = 1 + iota ///< Y Main - AOT_AAC_LC ///< Y Low Complexity - AOT_AAC_SSR ///< N (code in SoC repo) Scalable Sample Rate - AOT_AAC_LTP ///< Y Long Term Prediction - AOT_SBR ///< Y Spectral Band Replication - AOT_AAC_SCALABLE ///< N Scalable - AOT_TWINVQ ///< N Twin Vector Quantizer - AOT_CELP ///< N Code Excited Linear Prediction - AOT_HVXC ///< N Harmonic Vector eXcitation Coding - AOT_TTSI = 12 + iota ///< N Text-To-Speech Interface - AOT_MAINSYNTH ///< N Main Synthesis - AOT_WAVESYNTH ///< N Wavetable Synthesis - AOT_MIDI ///< N General MIDI - AOT_SAFX ///< N Algorithmic Synthesis and Audio Effects - AOT_ER_AAC_LC ///< N Error Resilient Low Complexity - AOT_ER_AAC_LTP = 19 + iota ///< N Error Resilient Long Term Prediction - AOT_ER_AAC_SCALABLE ///< N Error Resilient Scalable - AOT_ER_TWINVQ ///< N Error Resilient Twin Vector Quantizer - AOT_ER_BSAC ///< N Error Resilient Bit-Sliced Arithmetic Coding - AOT_ER_AAC_LD ///< N Error Resilient Low Delay - AOT_ER_CELP ///< N Error Resilient Code Excited Linear Prediction - AOT_ER_HVXC ///< N Error Resilient Harmonic Vector eXcitation Coding - AOT_ER_HILN ///< N Error Resilient Harmonic and Individual Lines plus Noise - AOT_ER_PARAM ///< N Error Resilient Parametric - AOT_SSC ///< N SinuSoidal Coding - AOT_PS ///< N Parametric Stereo - AOT_SURROUND ///< N MPEG Surround - AOT_ESCAPE ///< Y Escape Value - AOT_L1 ///< Y Layer 1 - AOT_L2 ///< Y Layer 2 - AOT_L3 ///< Y Layer 3 - AOT_DST ///< N Direct Stream Transfer - AOT_ALS ///< Y Audio LosslesS - AOT_SLS ///< N Scalable LosslesS - AOT_SLS_NON_CORE ///< N Scalable LosslesS (non core) - AOT_ER_AAC_ELD ///< N Error Resilient Enhanced Low Delay - AOT_SMR_SIMPLE ///< N Symbolic Music Representation Simple - AOT_SMR_MAIN ///< N Symbolic Music Representation Main - AOT_USAC_NOSBR ///< N Unified Speech and Audio Coding (no SBR) - AOT_SAOC ///< N Spatial Audio Object Coding - AOT_LD_SURROUND ///< N Low Delay MPEG Surround - AOT_USAC ///< N Unified Speech and Audio Coding -) - -type MPEG4AudioConfig struct { - SampleRate int - ChannelCount int - ObjectType uint - SampleRateIndex uint - ChannelConfig uint -} - -var sampleRateTable = []int{ - 96000, 88200, 64000, 48000, 44100, 32000, - 24000, 22050, 16000, 12000, 11025, 8000, 7350, -} - -var chanConfigTable = []int{ - 0, 1, 2, 3, 4, 5, 6, 8, -} - -func IsADTSFrame(frames []byte) bool { - return len(frames) > 7 && frames[0] == 0xff && frames[1]&0xf0 == 0xf0 -} - -func ReadADTSFrame(frame []byte) (config MPEG4AudioConfig, payload []byte, samples int, framelen int, err error) { - if !IsADTSFrame(frame) { - err = fmt.Errorf("not adts frame") - return - } - config.ObjectType = uint(frame[2]>>6) + 1 - config.SampleRateIndex = uint(frame[2] >> 2 & 0xf) - config.ChannelConfig = uint(frame[2]<<2&0x4 | frame[3]>>6&0x3) - framelen = int(frame[3]&0x3)<<11 | int(frame[4])<<3 | int(frame[5]>>5) - samples = (int(frame[6]&0x3) + 1) * 1024 - hdrlen := 7 - if frame[1]&0x1 == 0 { - hdrlen = 9 - } - if framelen < hdrlen || len(frame) < framelen { - err = fmt.Errorf("invalid adts header length") - return - } - payload = frame[hdrlen:framelen] - return -} - -func MakeADTSHeader(config MPEG4AudioConfig, samples int, payloadLength int) (header []byte) { - payloadLength += 7 - //AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ) - header = []byte{0xff, 0xf1, 0x50, 0x80, 0x043, 0xff, 0xcd} - //config.ObjectType = uint(frames[2]>>6)+1 - //config.SampleRateIndex = uint(frames[2]>>2&0xf) - //config.ChannelConfig = uint(frames[2]<<2&0x4|frames[3]>>6&0x3) - header[2] = (byte(config.ObjectType-1)&0x3)<<6 | (byte(config.SampleRateIndex)&0xf)<<2 | byte(config.ChannelConfig>>2)&0x1 - header[3] = header[3]&0x3f | byte(config.ChannelConfig&0x3)<<6 - header[3] = header[3]&0xfc | byte(payloadLength>>11)&0x3 - header[4] = byte(payloadLength >> 3) - header[5] = header[5]&0x1f | (byte(payloadLength)&0x7)<<5 - header[6] = header[6]&0xfc | byte(samples/1024-1) - return -} - -func ExtractADTSFrames(frames []byte) (config MPEG4AudioConfig, payload []byte, samples int, err error) { - for len(frames) > 0 { - var n, framelen int - if config, payload, n, framelen, err = ReadADTSFrame(frames); err != nil { - return - } - frames = frames[framelen:] - samples += n - } - return -} - -func ReadADTSHeader(data []byte) (config MPEG4AudioConfig, frameLength int) { - br := &bits.Reader{R: bytes.NewReader(data)} - var i uint - - //Structure - //AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ) - //Header consists of 7 or 9 bytes (without or with CRC). - - // 2 bytes - //A 12 syncword 0xFFF, all bits must be 1 - br.ReadBits(12) - //B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2 - br.ReadBits(1) - //C 2 Layer: always 0 - br.ReadBits(2) - //D 1 protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC - br.ReadBits(1) - - //E 2 profile, the MPEG-4 Audio Object Type minus 1 - config.ObjectType, _ = br.ReadBits(2) - config.ObjectType++ - //F 4 MPEG-4 Sampling Frequency Index (15 is forbidden) - config.SampleRateIndex, _ = br.ReadBits(4) - //G 1 private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding - br.ReadBits(1) - //H 3 MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE) - config.ChannelConfig, _ = br.ReadBits(3) - //I 1 originality, set to 0 when encoding, ignore when decoding - br.ReadBits(1) - //J 1 home, set to 0 when encoding, ignore when decoding - br.ReadBits(1) - //K 1 copyrighted id bit, the next bit of a centrally registered copyright identifier, set to 0 when encoding, ignore when decoding - br.ReadBits(1) - //L 1 copyright id start, signals that this frame's copyright id bit is the first bit of the copyright id, set to 0 when encoding, ignore when decoding - br.ReadBits(1) - - //M 13 frame length, this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame) - i, _ = br.ReadBits(13) - frameLength = int(i) - //O 11 Buffer fullness - br.ReadBits(11) - //P 2 Number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame - br.ReadBits(2) - - //Q 16 CRC if protection absent is 0 - return -} - -func readObjectType(r *bits.Reader) (objectType uint, err error) { - if objectType, err = r.ReadBits(5); err != nil { - return - } - if objectType == AOT_ESCAPE { - var i uint - if i, err = r.ReadBits(6); err != nil { - return - } - objectType = 32 + i - } - return -} - -func writeObjectType(w *bits.Writer, objectType uint) (err error) { - if objectType >= 32 { - if err = w.WriteBits(AOT_ESCAPE, 5); err != nil { - return - } - if err = w.WriteBits(objectType-32, 6); err != nil { - return - } - } else { - if err = w.WriteBits(objectType, 5); err != nil { - return - } - } - return -} - -func readSampleRateIndex(r *bits.Reader) (index uint, err error) { - if index, err = r.ReadBits(4); err != nil { - return - } - if index == 0xf { - if index, err = r.ReadBits(24); err != nil { - return - } - } - return -} - -func writeSampleRateIndex(w *bits.Writer, index uint) (err error) { - if index >= 0xf { - if err = w.WriteBits(0xf, 4); err != nil { - return - } - if err = w.WriteBits(index, 24); err != nil { - return - } - } else { - if err = w.WriteBits(index, 4); err != nil { - return - } - } - return -} - -func (self MPEG4AudioConfig) IsValid() bool { - return self.ObjectType > 0 -} - -func (self MPEG4AudioConfig) Complete() (config MPEG4AudioConfig) { - config = self - if int(config.SampleRateIndex) < len(sampleRateTable) { - config.SampleRate = sampleRateTable[config.SampleRateIndex] - } - if int(config.ChannelConfig) < len(chanConfigTable) { - config.ChannelCount = chanConfigTable[config.ChannelConfig] - } - return -} - -// copied from libavcodec/mpeg4audio.c avpriv_mpeg4audio_get_config() -func ReadMPEG4AudioConfig(r io.Reader) (config MPEG4AudioConfig, err error) { - br := &bits.Reader{R: r} - - if config.ObjectType, err = readObjectType(br); err != nil { - return - } - if config.SampleRateIndex, err = readSampleRateIndex(br); err != nil { - return - } - if config.ChannelConfig, err = br.ReadBits(4); err != nil { - return - } - return -} - -func WriteMPEG4AudioConfig(w io.Writer, config MPEG4AudioConfig) (err error) { - bw := &bits.Writer{W: w} - - if err = writeObjectType(bw, config.ObjectType); err != nil { - return - } - - if config.SampleRateIndex == 0 { - for i, rate := range sampleRateTable { - if rate == config.SampleRate { - config.SampleRateIndex = uint(i) - } - } - } - if err = writeSampleRateIndex(bw, config.SampleRateIndex); err != nil { - return - } - - if config.ChannelConfig == 0 { - for i, count := range chanConfigTable { - if count == config.ChannelCount { - config.ChannelConfig = uint(i) - } - } - } - if err = bw.WriteBits(config.ChannelConfig, 4); err != nil { - return - } - - if err = bw.FlushBits(); err != nil { - return - } - return -} - func readDesc(r io.Reader) (tag uint, data []byte, err error) { if tag, err = bits.ReadUIntBE(r, 8); err != nil { return @@ -518,28 +230,12 @@ func ReadElemStreamDesc(r io.Reader) (decConfig []byte, err error) { return } -func ReadElemStreamDescAAC(r io.Reader) (config MPEG4AudioConfig, err error) { - var data []byte - if data, err = ReadElemStreamDesc(r); err != nil { - return - } - if debugReader { - println("decConfig: ", len(data)) - } - if config, err = ReadMPEG4AudioConfig(bytes.NewReader(data)); err != nil { - return - } - return -} - -func WriteElemStreamDescAAC(w io.Writer, config MPEG4AudioConfig, trackId uint) (err error) { +func WriteElemStreamDesc(w io.Writer, decConfig []byte, trackId uint) (err error) { // MP4ESDescrTag(ESDesc MP4DecConfigDescrTag(objectId streamType bufSize avgBitrate MP4DecSpecificDescrTag(decConfig))) - buf := &bytes.Buffer{} - WriteMPEG4AudioConfig(buf, config) - data := buf.Bytes() + data := decConfig - buf = &bytes.Buffer{} + buf := &bytes.Buffer{} // 0x40 = ObjectType AAC // 0x15 = Audiostream writeDecConfDesc(buf, 0x40, 0x15, data) diff --git a/muxer.go b/muxer.go index 2d9e2e5..6c5ad64 100644 --- a/muxer.go +++ b/muxer.go @@ -7,6 +7,7 @@ import ( "github.com/nareix/mp4/atom" "github.com/nareix/mp4/isom" "github.com/nareix/codec/h264parser" + "github.com/nareix/codec/aacparser" "io" ) @@ -148,12 +149,12 @@ func (self *Muxer) WriteSample(pkt av.Packet) (err error) { pts, dts, isKeyFrame, frame := pkt.Pts, pkt.Dts, pkt.IsKeyFrame, pkt.Data stream := self.streams[pkt.StreamIdx] - if stream.Type() == av.AAC && isom.IsADTSFrame(frame) { + if stream.Type() == av.AAC && aacparser.IsADTSFrame(frame) { for len(frame) > 0 { var payload []byte var samples int var framelen int - if _, payload, samples, framelen, err = isom.ReadADTSFrame(frame); err != nil { + if _, payload, samples, framelen, err = aacparser.ReadADTSFrame(frame); err != nil { return } delta := int64(samples) * stream.TimeScale() / int64(stream.SampleRate()) @@ -247,7 +248,7 @@ func (self *Muxer) WriteTrailer() (err error) { if err = stream.fillTrackAtom(); err != nil { return } - dur := stream.Duration() + dur := stream.duration() stream.trackAtom.Header.Duration = int(float64(timeScale) * dur) if dur > maxDur { maxDur = dur diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..b1126b2 --- /dev/null +++ b/stream.go @@ -0,0 +1,41 @@ +package mp4 + +import ( + "github.com/nareix/av" + "github.com/nareix/mp4/atom" + "io" +) + +type Stream struct { + av.StreamCommon + + trackAtom *atom.Track + r io.ReadSeeker + idx int + + muxer *Muxer + + sample *atom.SampleTable + sampleIndex int + + sampleOffsetInChunk int64 + syncSampleIndex int + + dts int64 + sttsEntryIndex int + sampleIndexInSttsEntry int + + cttsEntryIndex int + sampleIndexInCttsEntry int + + chunkGroupIndex int + chunkIndex int + sampleIndexInChunk int + + sttsEntry *atom.TimeToSampleEntry + cttsEntry *atom.CompositionOffsetEntry + writeMdat func([]byte) (int64, error) + lastDts int64 +} + + From 0162e613e6b6a24e7190f7fad80ed12148b93e8d Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 19 Apr 2016 19:08:32 +0800 Subject: [PATCH 64/93] fix SeekToTime() bug --- demuxer.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/demuxer.go b/demuxer.go index 82f0b48..3cec635 100644 --- a/demuxer.go +++ b/demuxer.go @@ -118,7 +118,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { } } if !found { - err = io.EOF + err = fmt.Errorf("stream[%d]: cannot locate sample index in chunk", self.idx) return } @@ -126,7 +126,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { self.sampleOffsetInChunk = int64(self.sampleIndexInChunk * self.sample.SampleSize.SampleSize) } else { if index >= len(self.sample.SampleSize.Entries) { - err = io.EOF + err = fmt.Errorf("stream[%d]: sample index out of range", self.idx) return } self.sampleOffsetInChunk = int64(0) @@ -145,6 +145,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { if index >= start && index < start+n { self.sampleIndexInSttsEntry = index - start self.dts += int64((index - start) * entry.Duration) + found = true break } start += n @@ -152,7 +153,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { self.sttsEntryIndex++ } if !found { - err = io.EOF + err = fmt.Errorf("stream[%d]: cannot locate sample index in stts entry", self.idx) return } @@ -164,13 +165,14 @@ func (self *Stream) setSampleIndex(index int) (err error) { n := self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count if index >= start && index < start+n { self.sampleIndexInCttsEntry = index - start + found = true break } start += n self.cttsEntryIndex++ } if !found { - err = io.EOF + err = fmt.Errorf("stream[%d]: cannot locate sample index in ctts entry", self.idx) return } } @@ -283,10 +285,13 @@ func (self *Stream) sampleCount() int { func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { var choose *Stream for _, stream := range(self.streams) { - if choose == nil || stream.dts < choose.dts { + if choose == nil || stream.TsToTime(stream.dts) < choose.TsToTime(choose.dts) { choose = stream } } + if false { + fmt.Printf("ReadPacket: choose index=%v time=%v\n", choose.idx, choose.TsToTime(choose.dts)) + } pkt.StreamIdx = choose.idx pkt.Pts, pkt.Dts, pkt.IsKeyFrame, pkt.Data, err = choose.readSample() return @@ -351,9 +356,15 @@ func (self *Stream) duration() float64 { return float64(total) / float64(self.TimeScale()) } -func (self *Stream) seekToTime(time float64) error { +func (self *Stream) seekToTime(time float64) (err error) { index := self.timeToSampleIndex(time) - return self.setSampleIndex(index) + if err = self.setSampleIndex(index); err != nil { + return + } + if false { + fmt.Printf("stream[%d]: seekToTime index=%v time=%v cur=%v\n", self.idx, index, time, self.TsToTime(self.dts)) + } + return } func (self *Stream) timeToSampleIndex(time float64) int { From 3a136cd8f5f4fd9892f5b944db8cd4449eac896b Mon Sep 17 00:00:00 2001 From: nareix Date: Tue, 19 Apr 2016 19:19:03 +0800 Subject: [PATCH 65/93] keep AV sync after seek --- demuxer.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/demuxer.go b/demuxer.go index 3cec635..5f62125 100644 --- a/demuxer.go +++ b/demuxer.go @@ -299,10 +299,23 @@ func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { func (self *Demuxer) SeekToTime(time float64) (err error) { for _, stream := range(self.streams) { - if err = stream.seekToTime(time); err != nil { - return + if stream.IsVideo() { + if err = stream.seekToTime(time); err != nil { + return + } + time = stream.TsToTime(stream.dts) + break } } + + for _, stream := range(self.streams) { + if !stream.IsVideo() { + if err = stream.seekToTime(time); err != nil { + return + } + } + } + return } From 7d1af5845160a6c07d65909d33645af8b47149a3 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 21 Apr 2016 15:58:22 +0800 Subject: [PATCH 66/93] api change --- demuxer.go | 70 +++++++++++++++++++++++++------------------------ muxer.go | 77 ++++++++++++++++++++---------------------------------- stream.go | 12 +++++++-- 3 files changed, 75 insertions(+), 84 deletions(-) diff --git a/demuxer.go b/demuxer.go index 5f62125..edbdfb5 100644 --- a/demuxer.go +++ b/demuxer.go @@ -17,7 +17,7 @@ type Demuxer struct { } func (self *Demuxer) Streams() (streams []av.Stream) { - for _, stream := range(self.streams) { + for _, stream := range self.streams { streams = append(streams, stream) } return @@ -69,7 +69,7 @@ func (self *Demuxer) ReadHeader() (err error) { } if atrack.Media != nil && atrack.Media.Info != nil && atrack.Media.Info.Sample != nil { stream.sample = atrack.Media.Info.Sample - stream.SetTimeScale(int64(atrack.Media.Header.TimeScale)) + stream.timeScale = int64(atrack.Media.Header.TimeScale) } else { err = fmt.Errorf("sample table not found") return @@ -219,7 +219,7 @@ func (self *Stream) isSampleValid() bool { return true } -func (self *Stream) incSampleIndex() { +func (self *Stream) incSampleIndex() (duration int64) { self.sampleIndexInChunk++ if self.sampleIndexInChunk == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk { self.chunkIndex++ @@ -239,8 +239,9 @@ func (self *Stream) incSampleIndex() { } sttsEntry := self.sample.TimeToSample.Entries[self.sttsEntryIndex] + duration = int64(sttsEntry.Duration) self.sampleIndexInSttsEntry++ - self.dts += int64(sttsEntry.Duration) + self.dts += duration if self.sampleIndexInSttsEntry == sttsEntry.Count { self.sampleIndexInSttsEntry = 0 self.sttsEntryIndex++ @@ -262,6 +263,7 @@ func (self *Stream) incSampleIndex() { } self.sampleIndex++ + return } func (self *Stream) sampleCount() int { @@ -282,33 +284,41 @@ func (self *Stream) sampleCount() int { } } -func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { +func (self *Demuxer) ReadPacket() (streamIndex int, pkt av.Packet, err error) { var choose *Stream - for _, stream := range(self.streams) { - if choose == nil || stream.TsToTime(stream.dts) < choose.TsToTime(choose.dts) { + for i, stream := range self.streams { + if choose == nil || stream.tsToTime(stream.dts) < choose.tsToTime(choose.dts) { choose = stream + streamIndex = i } } if false { - fmt.Printf("ReadPacket: choose index=%v time=%v\n", choose.idx, choose.TsToTime(choose.dts)) + fmt.Printf("ReadPacket: choose index=%v time=%v\n", choose.idx, choose.tsToTime(choose.dts)) + } + pkt, err = choose.readPacket() + return +} + +func (self *Demuxer) Time() (time float64) { + if len(self.streams) > 0 { + stream := self.streams[0] + time = stream.tsToTime(stream.dts) } - pkt.StreamIdx = choose.idx - pkt.Pts, pkt.Dts, pkt.IsKeyFrame, pkt.Data, err = choose.readSample() return } func (self *Demuxer) SeekToTime(time float64) (err error) { - for _, stream := range(self.streams) { + for _, stream := range self.streams { if stream.IsVideo() { if err = stream.seekToTime(time); err != nil { return } - time = stream.TsToTime(stream.dts) + time = stream.tsToTime(stream.dts) break } } - for _, stream := range(self.streams) { + for _, stream := range self.streams { if !stream.IsVideo() { if err = stream.seekToTime(time); err != nil { return @@ -319,11 +329,12 @@ func (self *Demuxer) SeekToTime(time float64) (err error) { return } -func (self *Stream) readSample() (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { +func (self *Stream) readPacket() (pkt av.Packet, err error) { if !self.isSampleValid() { err = io.EOF return } + //fmt.Println("readPacket", self.sampleIndex) chunkOffset := self.sample.ChunkOffset.Entries[self.chunkIndex] sampleSize := 0 @@ -334,54 +345,46 @@ func (self *Stream) readSample() (pts int64, dts int64, isKeyFrame bool, data [] } sampleOffset := int64(chunkOffset) + self.sampleOffsetInChunk - if _, err = self.r.Seek(int64(sampleOffset), 0); err != nil { + if _, err = self.r.Seek(sampleOffset, 0); err != nil { return } - data = make([]byte, sampleSize) - if _, err = self.r.Read(data); err != nil { + pkt.Data = make([]byte, sampleSize) + if _, err = self.r.Read(pkt.Data); err != nil { return } if self.sample.SyncSample != nil { if self.sample.SyncSample.Entries[self.syncSampleIndex]-1 == self.sampleIndex { - isKeyFrame = true + pkt.IsKeyFrame = true } } //println("pts/dts", self.ptsEntryIndex, self.dtsEntryIndex) - dts = self.dts if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 { - pts = self.dts + int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset) - } else { - pts = dts + cts := int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset) + pkt.CompositionTime = self.tsToTime(cts) } - self.incSampleIndex() + duration := self.incSampleIndex() + pkt.Duration = self.tsToTime(duration) + return } -func (self *Stream) duration() float64 { - total := int64(0) - for _, entry := range self.sample.TimeToSample.Entries { - total += int64(entry.Duration * entry.Count) - } - return float64(total) / float64(self.TimeScale()) -} - func (self *Stream) seekToTime(time float64) (err error) { index := self.timeToSampleIndex(time) if err = self.setSampleIndex(index); err != nil { return } if false { - fmt.Printf("stream[%d]: seekToTime index=%v time=%v cur=%v\n", self.idx, index, time, self.TsToTime(self.dts)) + fmt.Printf("stream[%d]: seekToTime index=%v time=%v cur=%v\n", self.idx, index, time, self.tsToTime(self.dts)) } return } func (self *Stream) timeToSampleIndex(time float64) int { - targetTs := self.TimeToTs(time) + targetTs := self.timeToTs(time) targetIndex := 0 startTs := int64(0) @@ -419,4 +422,3 @@ func (self *Stream) timeToSampleIndex(time float64) int { return targetIndex } - diff --git a/muxer.go b/muxer.go index 6c5ad64..f60436a 100644 --- a/muxer.go +++ b/muxer.go @@ -4,10 +4,10 @@ import ( "bytes" "fmt" "github.com/nareix/av" + "github.com/nareix/codec/aacparser" + "github.com/nareix/codec/h264parser" "github.com/nareix/mp4/atom" "github.com/nareix/mp4/isom" - "github.com/nareix/codec/h264parser" - "github.com/nareix/codec/aacparser" "io" ) @@ -62,6 +62,7 @@ func (self *Muxer) NewStream() av.Stream { }, } + stream.timeScale = 90000 stream.muxer = self self.streams = append(self.streams, stream) @@ -69,12 +70,8 @@ func (self *Muxer) NewStream() av.Stream { } func (self *Stream) fillTrackAtom() (err error) { - if self.sampleIndex > 0 { - self.sttsEntry.Count++ - } - - self.trackAtom.Media.Header.TimeScale = int(self.TimeScale()) - self.trackAtom.Media.Header.Duration = int(self.lastDts) + self.trackAtom.Media.Header.TimeScale = int(self.timeScale) + self.trackAtom.Media.Header.Duration = int(self.duration) if self.Type() == av.H264 { width, height := self.Width(), self.Height() @@ -130,14 +127,6 @@ func (self *Stream) fillTrackAtom() (err error) { return } -func (self *Muxer) writeMdat(data []byte) (pos int64, err error) { - if pos, err = self.mdatWriter.Seek(0, 1); err != nil { - return - } - _, err = self.mdatWriter.Write(data) - return -} - func (self *Muxer) WriteHeader() (err error) { if self.mdatWriter, err = atom.WriteAtomHeader(self.W, "mdat"); err != nil { return @@ -145,11 +134,12 @@ func (self *Muxer) WriteHeader() (err error) { return } -func (self *Muxer) WriteSample(pkt av.Packet) (err error) { - pts, dts, isKeyFrame, frame := pkt.Pts, pkt.Dts, pkt.IsKeyFrame, pkt.Data - stream := self.streams[pkt.StreamIdx] +func (self *Muxer) WritePacket(streamIndex int, pkt av.Packet) (err error) { + stream := self.streams[streamIndex] + frame := pkt.Data if stream.Type() == av.AAC && aacparser.IsADTSFrame(frame) { + sampleRate := stream.SampleRate() for len(frame) > 0 { var payload []byte var samples int @@ -157,20 +147,21 @@ func (self *Muxer) WriteSample(pkt av.Packet) (err error) { if _, payload, samples, framelen, err = aacparser.ReadADTSFrame(frame); err != nil { return } - delta := int64(samples) * stream.TimeScale() / int64(stream.SampleRate()) - pts += delta - dts += delta - frame = frame[framelen:] - if stream.writeSample(pts, dts, isKeyFrame, payload); err != nil { + newpkt := pkt + newpkt.Data = payload + newpkt.Duration = float64(samples)/float64(sampleRate) + if err = stream.writePacket(newpkt); err != nil { return } + frame = frame[framelen:] } + return } - return stream.writeSample(pts, dts, isKeyFrame, frame) + return stream.writePacket(pkt) } -func (self *Stream) writeSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { +func (self *Stream) writePacket(pkt av.Packet) (err error) { var filePos int64 var sampleSize int @@ -179,7 +170,7 @@ func (self *Stream) writeSample(pts int64, dts int64, isKeyFrame bool, data []by } if self.Type() == av.H264 { - nalus, _ := h264parser.SplitNALUs(data) + nalus, _ := h264parser.SplitNALUs(pkt.Data) h264parser.WalkNALUsAVCC(nalus, func(b []byte) { sampleSize += len(b) _, err = self.muxer.mdatWriter.Write(b) @@ -188,35 +179,25 @@ func (self *Stream) writeSample(pts int64, dts int64, isKeyFrame bool, data []by return } } else { - sampleSize = len(data) - if _, err = self.muxer.mdatWriter.Write(data); err != nil { + sampleSize = len(pkt.Data) + if _, err = self.muxer.mdatWriter.Write(pkt.Data); err != nil { return } } - if isKeyFrame && self.sample.SyncSample != nil { + if pkt.IsKeyFrame && self.sample.SyncSample != nil { self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, self.sampleIndex+1) } - if self.sampleIndex > 0 { - if dts <= self.lastDts { - err = fmt.Errorf("dts must be incremental") - return - } - duration := int(dts - self.lastDts) - if self.sttsEntry == nil || duration != self.sttsEntry.Duration { - self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, atom.TimeToSampleEntry{Duration: duration}) - self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1] - } - self.sttsEntry.Count++ + duration := int(self.timeToTs(pkt.Duration)) + if self.sttsEntry == nil || duration != self.sttsEntry.Duration { + self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, atom.TimeToSampleEntry{Duration: duration}) + self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1] } + self.sttsEntry.Count++ if self.sample.CompositionOffset != nil { - if pts < dts { - err = fmt.Errorf("pts must greater than dts") - return - } - offset := int(pts - dts) + offset := int(self.timeToTs(pkt.CompositionTime)) if self.cttsEntry == nil || offset != self.cttsEntry.Offset { table := self.sample.CompositionOffset table.Entries = append(table.Entries, atom.CompositionOffsetEntry{Offset: offset}) @@ -225,7 +206,7 @@ func (self *Stream) writeSample(pts int64, dts int64, isKeyFrame bool, data []by self.cttsEntry.Count++ } - self.lastDts = dts + self.duration += int64(duration) self.sampleIndex++ self.sample.ChunkOffset.Entries = append(self.sample.ChunkOffset.Entries, int(filePos)) self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, sampleSize) @@ -248,7 +229,7 @@ func (self *Muxer) WriteTrailer() (err error) { if err = stream.fillTrackAtom(); err != nil { return } - dur := stream.duration() + dur := stream.tsToTime(stream.duration) stream.trackAtom.Header.Duration = int(float64(timeScale) * dur) if dur > maxDur { maxDur = dur diff --git a/stream.go b/stream.go index b1126b2..5252423 100644 --- a/stream.go +++ b/stream.go @@ -11,7 +11,10 @@ type Stream struct { trackAtom *atom.Track r io.ReadSeeker - idx int + idx int + + timeScale int64 + duration int64 muxer *Muxer @@ -35,7 +38,12 @@ type Stream struct { sttsEntry *atom.TimeToSampleEntry cttsEntry *atom.CompositionOffsetEntry writeMdat func([]byte) (int64, error) - lastDts int64 } +func (self *Stream) timeToTs(time float64) int64 { + return int64(time * float64(self.timeScale)) +} +func (self *Stream) tsToTime(ts int64) float64 { + return float64(ts) / float64(self.timeScale) +} From 2364b70034bd376f2da99f2cfbab9877f3a04eb1 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 21 Apr 2016 19:13:48 +0800 Subject: [PATCH 67/93] fix setSampleIndex search chunkGroup bug --- demuxer.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/demuxer.go b/demuxer.go index edbdfb5..2780b7c 100644 --- a/demuxer.go +++ b/demuxer.go @@ -105,6 +105,10 @@ func (self *Stream) setSampleIndex(index int) (err error) { self.chunkGroupIndex = 0 for self.chunkIndex = range self.sample.ChunkOffset.Entries { + if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) && + self.chunkIndex+1 == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk { + self.chunkGroupIndex++ + } n := self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk if index >= start && index < start+n { found = true @@ -112,10 +116,6 @@ func (self *Stream) setSampleIndex(index int) (err error) { break } start += n - if self.chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) && - self.chunkIndex+1 == self.sample.SampleToChunk.Entries[self.chunkGroupIndex+1].FirstChunk { - self.chunkGroupIndex++ - } } if !found { err = fmt.Errorf("stream[%d]: cannot locate sample index in chunk", self.idx) @@ -187,6 +187,11 @@ func (self *Stream) setSampleIndex(index int) (err error) { } } + if false { + fmt.Printf("stream[%d]: setSampleIndex chunkGroupIndex=%d chunkIndex=%d sampleOffsetInChunk=%d\n", + self.idx, self.chunkGroupIndex, self.chunkIndex, self.sampleOffsetInChunk) + } + self.sampleIndex = index return } @@ -220,6 +225,10 @@ func (self *Stream) isSampleValid() bool { } func (self *Stream) incSampleIndex() (duration int64) { + if false { + fmt.Printf("incSampleIndex sampleIndex=%d sampleOffsetInChunk=%d sampleIndexInChunk=%d chunkGroupIndex=%d chunkIndex=%d\n", + self.sampleIndex, self.sampleOffsetInChunk, self.sampleIndexInChunk, self.chunkGroupIndex, self.chunkIndex) + } self.sampleIndexInChunk++ if self.sampleIndexInChunk == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk { self.chunkIndex++ From c7890dcc2e209aafe8a297b57880ca888d33b237 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 21 Apr 2016 21:17:51 +0800 Subject: [PATCH 68/93] fix video no ctts bug --- demuxer.go | 1 + muxer.go | 6 +++++- stream.go | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/demuxer.go b/demuxer.go index 2780b7c..3304bd2 100644 --- a/demuxer.go +++ b/demuxer.go @@ -229,6 +229,7 @@ func (self *Stream) incSampleIndex() (duration int64) { fmt.Printf("incSampleIndex sampleIndex=%d sampleOffsetInChunk=%d sampleIndexInChunk=%d chunkGroupIndex=%d chunkIndex=%d\n", self.sampleIndex, self.sampleOffsetInChunk, self.sampleIndexInChunk, self.chunkGroupIndex, self.chunkIndex) } + self.sampleIndexInChunk++ if self.sampleIndexInChunk == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk { self.chunkIndex++ diff --git a/muxer.go b/muxer.go index f60436a..82a55a9 100644 --- a/muxer.go +++ b/muxer.go @@ -91,7 +91,6 @@ func (self *Stream) fillTrackAtom() (err error) { SubType: "vide", Name: "Video Media Handler", } - self.sample.CompositionOffset = &atom.CompositionOffset{} self.trackAtom.Media.Info.Video = &atom.VideoMediaInfo{ Flags: 0x000001, } @@ -131,6 +130,11 @@ func (self *Muxer) WriteHeader() (err error) { if self.mdatWriter, err = atom.WriteAtomHeader(self.W, "mdat"); err != nil { return } + for _, stream := range self.streams { + if stream.Type().IsVideo() { + stream.sample.CompositionOffset = &atom.CompositionOffset{} + } + } return } diff --git a/stream.go b/stream.go index 5252423..550b398 100644 --- a/stream.go +++ b/stream.go @@ -37,7 +37,6 @@ type Stream struct { sttsEntry *atom.TimeToSampleEntry cttsEntry *atom.CompositionOffsetEntry - writeMdat func([]byte) (int64, error) } func (self *Stream) timeToTs(time float64) int64 { From 25ee94037ef79741a143c3afa0407d6b10e4ba20 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 4 May 2016 11:33:02 +0800 Subject: [PATCH 69/93] rm example --- example/example.go | 87 ---------------------------------------------- 1 file changed, 87 deletions(-) delete mode 100644 example/example.go diff --git a/example/example.go b/example/example.go deleted file mode 100644 index 61510df..0000000 --- a/example/example.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "encoding/hex" - "fmt" - "github.com/nareix/mp4" - "os" -) - -func DemuxExample() { - file, _ := os.Open("test.mp4") - demuxer := &mp4.Demuxer{R: file} - demuxer.ReadHeader() - - fmt.Println("Total tracks: ", len(demuxer.Tracks)) - fmt.Println("Duration: ", demuxer.TrackH264.Duration()) - - count := demuxer.TrackH264.SampleCount() - fmt.Println("SampleCount: ", count) - - demuxer.TrackH264.SeekToTime(2.3) - - var sample []byte - for i := 0; i < 5; i++ { - pts, dts, isKeyFrame, data, err := demuxer.TrackH264.ReadSample() - fmt.Println("sample #", - i, pts, dts, isKeyFrame, len(data), - demuxer.TrackH264.CurTime(), - err, - ) - if i == 3 { - sample = data - } - } - fmt.Println("Sample H264 frame:") - fmt.Print(hex.Dump(sample)) - - fmt.Println("Duration(AAC): ", demuxer.TrackAAC.Duration()) - fmt.Println("SampleCount(AAC): ", demuxer.TrackAAC.SampleCount()) - demuxer.TrackAAC.SeekToTime(1.3) - - for i := 0; i < 5; i++ { - pts, dts, isKeyFrame, data, err := demuxer.TrackAAC.ReadSample() - fmt.Println("sample(AAC) #", - i, pts, dts, isKeyFrame, len(data), - demuxer.TrackAAC.CurTime(), - err, - ) - if i == 1 { - sample = data - } - } - fmt.Println("Sample AAC frame:") - fmt.Print(hex.Dump(sample)) -} - -func MuxExample() { - infile, _ := os.Open("test.mp4") - outfile, _ := os.Create("test.out.mp4") - - demuxer := &mp4.Demuxer{R: infile} - demuxer.ReadHeader() - - muxer := &mp4.Muxer{W: outfile} - muxer.AddH264Track() - muxer.TrackH264.SetH264PPS(demuxer.TrackH264.GetH264PPS()) - muxer.TrackH264.SetH264SPS(demuxer.TrackH264.GetH264SPS()) - muxer.TrackH264.SetTimeScale(demuxer.TrackH264.TimeScale()) - - muxer.WriteHeader() - for { - pts, dts, isKeyFrame, data, err := demuxer.TrackH264.ReadSample() - if err != nil { - break - } - fmt.Println("sample #", dts, pts) - muxer.TrackH264.WriteSample(pts, dts, isKeyFrame, data) - } - - muxer.WriteTrailer() - outfile.Close() -} - -func main() { - //DemuxExample() - MuxExample() -} From eec4063cf9b9dbb2f4f92a2b6fd053ec61de673d Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 25 May 2016 07:44:31 +0800 Subject: [PATCH 70/93] adjust newapi --- demuxer.go | 29 +++++++++++++++++++---------- muxer.go | 41 +++++++++++++++++++++++++++++------------ stream.go | 2 +- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/demuxer.go b/demuxer.go index 3304bd2..8909547 100644 --- a/demuxer.go +++ b/demuxer.go @@ -6,9 +6,20 @@ import ( "github.com/nareix/av" "github.com/nareix/mp4/atom" "github.com/nareix/mp4/isom" + "github.com/nareix/codec/aacparser" + "github.com/nareix/codec/h264parser" "io" ) +func Open(R io.ReadSeeker) (demuxer *Demuxer, err error) { + _demuxer := &Demuxer{R: R} + if err = _demuxer.ReadHeader(); err != nil { + return + } + demuxer = _demuxer + return +} + type Demuxer struct { R io.ReadSeeker @@ -16,7 +27,7 @@ type Demuxer struct { movieAtom *atom.Movie } -func (self *Demuxer) Streams() (streams []av.Stream) { +func (self *Demuxer) Streams() (streams []av.CodecData) { for _, stream := range self.streams { streams = append(streams, stream) } @@ -76,19 +87,17 @@ func (self *Demuxer) ReadHeader() (err error) { } if avc1 := atom.GetAvc1ConfByTrack(atrack); avc1 != nil { - stream.SetType(av.H264) - if err = stream.SetCodecData(avc1.Data); err != nil { + if stream.CodecData, err = h264parser.NewCodecDataFromAVCDecoderConfRecord(avc1.Data); err != nil { return } self.streams = append(self.streams, stream) } else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil && mp4a.Conf != nil { - stream.SetType(av.AAC) var config []byte if config, err = isom.ReadElemStreamDesc(bytes.NewReader(mp4a.Conf.Data)); err != nil { return } - if err = stream.SetCodecData(config); err != nil { + if stream.CodecData, err = aacparser.NewCodecDataFromMPEG4AudioConfigBytes(config); err != nil { return } self.streams = append(self.streams, stream) @@ -295,17 +304,17 @@ func (self *Stream) sampleCount() int { } func (self *Demuxer) ReadPacket() (streamIndex int, pkt av.Packet, err error) { - var choose *Stream + var chosen *Stream for i, stream := range self.streams { - if choose == nil || stream.tsToTime(stream.dts) < choose.tsToTime(choose.dts) { - choose = stream + if chosen == nil || stream.tsToTime(stream.dts) < chosen.tsToTime(chosen.dts) { + chosen = stream streamIndex = i } } if false { - fmt.Printf("ReadPacket: choose index=%v time=%v\n", choose.idx, choose.tsToTime(choose.dts)) + fmt.Printf("ReadPacket: chosen index=%v time=%v\n", chosen.idx, chosen.tsToTime(chosen.dts)) } - pkt, err = choose.readPacket() + pkt, err = chosen.readPacket() return } diff --git a/muxer.go b/muxer.go index 82a55a9..058f422 100644 --- a/muxer.go +++ b/muxer.go @@ -11,14 +11,23 @@ import ( "io" ) +func Create(W io.WriteSeeker, streams []av.CodecData) (muxer *Muxer, err error) { + _muxer := &Muxer{W: W} + if err = _muxer.WriteHeader(streams); err != nil { + return + } + muxer = _muxer + return +} + type Muxer struct { W io.WriteSeeker streams []*Stream mdatWriter *atom.Writer } -func (self *Muxer) NewStream() av.Stream { - stream := &Stream{} +func (self *Muxer) NewStream(codec av.CodecData) (err error) { + stream := &Stream{CodecData: codec} stream.sample = &atom.SampleTable{ SampleDesc: &atom.SampleDesc{}, @@ -66,7 +75,7 @@ func (self *Muxer) NewStream() av.Stream { stream.muxer = self self.streams = append(self.streams, stream) - return stream + return } func (self *Stream) fillTrackAtom() (err error) { @@ -74,7 +83,8 @@ func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Media.Header.Duration = int(self.duration) if self.Type() == av.H264 { - width, height := self.Width(), self.Height() + codec := self.CodecData.(av.H264CodecData) + width, height := codec.Width(), codec.Height() self.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ DataRefIdx: 1, HorizontalResolution: 72, @@ -84,7 +94,7 @@ func (self *Stream) fillTrackAtom() (err error) { FrameCount: 1, Depth: 24, ColorTableId: -1, - Conf: &atom.Avc1Conf{Data: self.CodecData()}, + Conf: &atom.Avc1Conf{Data: codec.AVCDecoderConfRecordBytes()}, } self.sample.SyncSample = &atom.SyncSample{} self.trackAtom.Media.Handler = &atom.HandlerRefer{ @@ -98,15 +108,16 @@ func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Header.TrackHeight = atom.IntToFixed(int(height)) } else if self.Type() == av.AAC { + codec := self.CodecData.(av.AACCodecData) buf := &bytes.Buffer{} - if err = isom.WriteElemStreamDesc(buf, self.CodecData(), uint(self.trackAtom.Header.TrackId)); err != nil { + if err = isom.WriteElemStreamDesc(buf, codec.MPEG4AudioConfigBytes(), uint(self.trackAtom.Header.TrackId)); err != nil { return } self.sample.SampleDesc.Mp4aDesc = &atom.Mp4aDesc{ DataRefIdx: 1, - NumberOfChannels: self.ChannelCount(), - SampleSize: self.ChannelCount() * 8, - SampleRate: atom.IntToFixed(self.SampleRate()), + NumberOfChannels: codec.ChannelCount(), + SampleSize: codec.ChannelCount() * 8, + SampleRate: atom.IntToFixed(codec.SampleRate()), Conf: &atom.ElemStreamDesc{ Data: buf.Bytes(), }, @@ -126,12 +137,18 @@ func (self *Stream) fillTrackAtom() (err error) { return } -func (self *Muxer) WriteHeader() (err error) { +func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) { + for _, stream := range streams { + if err = self.NewStream(stream); err != nil { + return + } + } + if self.mdatWriter, err = atom.WriteAtomHeader(self.W, "mdat"); err != nil { return } for _, stream := range self.streams { - if stream.Type().IsVideo() { + if stream.IsVideo() { stream.sample.CompositionOffset = &atom.CompositionOffset{} } } @@ -143,7 +160,7 @@ func (self *Muxer) WritePacket(streamIndex int, pkt av.Packet) (err error) { frame := pkt.Data if stream.Type() == av.AAC && aacparser.IsADTSFrame(frame) { - sampleRate := stream.SampleRate() + sampleRate := stream.CodecData.(av.AudioCodecData).SampleRate() for len(frame) > 0 { var payload []byte var samples int diff --git a/stream.go b/stream.go index 550b398..2e6cdf2 100644 --- a/stream.go +++ b/stream.go @@ -7,7 +7,7 @@ import ( ) type Stream struct { - av.StreamCommon + av.CodecData trackAtom *atom.Track r io.ReadSeeker From 3f00344bcf5afc3d4fdce455249e0273c62c885a Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 26 May 2016 20:14:36 +0800 Subject: [PATCH 71/93] add IsCodecSupported --- muxer.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/muxer.go b/muxer.go index 058f422..a18f38e 100644 --- a/muxer.go +++ b/muxer.go @@ -26,7 +26,21 @@ type Muxer struct { mdatWriter *atom.Writer } +func (self *Muxer) IsCodecSupported(codec av.CodecData) bool { + switch codec.Type() { + case av.H264, av.AAC: + return true + default: + return false + } +} + func (self *Muxer) NewStream(codec av.CodecData) (err error) { + if !self.IsCodecSupported(codec) { + err = fmt.Errorf("codec type=%x is not supported", codec.Type()) + return + } + stream := &Stream{CodecData: codec} stream.sample = &atom.SampleTable{ From 5729c1dda5a13bb9666137f148ada384dcdef7ce Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 26 May 2016 20:14:45 +0800 Subject: [PATCH 72/93] add CurrentTime --- demuxer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demuxer.go b/demuxer.go index 8909547..e9e1a5e 100644 --- a/demuxer.go +++ b/demuxer.go @@ -318,7 +318,7 @@ func (self *Demuxer) ReadPacket() (streamIndex int, pkt av.Packet, err error) { return } -func (self *Demuxer) Time() (time float64) { +func (self *Demuxer) CurrentTime() (time float64) { if len(self.streams) > 0 { stream := self.streams[0] time = stream.tsToTime(stream.dts) From baa18a47313b0cc27f7d0ab0ee8235aed7ecb519 Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 5 Jun 2016 20:02:20 +0800 Subject: [PATCH 73/93] use BytesPerSample --- muxer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/muxer.go b/muxer.go index a18f38e..8a1afa8 100644 --- a/muxer.go +++ b/muxer.go @@ -129,8 +129,8 @@ func (self *Stream) fillTrackAtom() (err error) { } self.sample.SampleDesc.Mp4aDesc = &atom.Mp4aDesc{ DataRefIdx: 1, - NumberOfChannels: codec.ChannelCount(), - SampleSize: codec.ChannelCount() * 8, + NumberOfChannels: codec.ChannelLayout().Count(), + SampleSize: codec.SampleFormat().BytesPerSample(), SampleRate: atom.IntToFixed(codec.SampleRate()), Conf: &atom.ElemStreamDesc{ Data: buf.Bytes(), From 4d6509b2672418762edaa4d85e40612343aa9570 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 8 Jun 2016 14:48:23 +0800 Subject: [PATCH 74/93] rename av.H264CodecData --- muxer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/muxer.go b/muxer.go index 8a1afa8..7cda260 100644 --- a/muxer.go +++ b/muxer.go @@ -97,7 +97,7 @@ func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Media.Header.Duration = int(self.duration) if self.Type() == av.H264 { - codec := self.CodecData.(av.H264CodecData) + codec := self.CodecData.(h264parser.CodecData) width, height := codec.Width(), codec.Height() self.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ DataRefIdx: 1, @@ -122,7 +122,7 @@ func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Header.TrackHeight = atom.IntToFixed(int(height)) } else if self.Type() == av.AAC { - codec := self.CodecData.(av.AACCodecData) + codec := self.CodecData.(aacparser.CodecData) buf := &bytes.Buffer{} if err = isom.WriteElemStreamDesc(buf, codec.MPEG4AudioConfigBytes(), uint(self.trackAtom.Header.TrackId)); err != nil { return From 6a3c7699804d926414f3afb9cc0ae378f015f3a0 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 8 Jun 2016 17:53:13 +0800 Subject: [PATCH 75/93] remove readme --- README.md | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 23c2910..0000000 --- a/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# A pure golang mp4 library - -Provides mp4 reader/writer and mp4 atom manipulations functions. - -Open a mp4 file and read the first sample: -```go -file, _ := os.Open("test.mp4") -demuxer := &mp4.Demuxer{R: file} -demuxer.ReadHeader() -pts, dts, isKeyFrame, data, err := demuxer.TrackH264.ReadSample() -``` - -do some seeking: - -```go -demuxer.TrackH264.SeekToTime(2.0) -``` - -demuxer demo code [here](https://github.com/nareix/mp4/blob/master/example/example.go#L11) - -the library also provide atom struct decoding/encoding functions( -learn more about mp4 atoms [here](https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html) -) - -you can access atom structs via `Demuxer.TrackH264.TrackAtom`. for example: - -```go -// Get the raw TimeScale field inside `mvhd` atom -fmt.Println(demuxer.TrackH264.TrackAtom.Media.Header.TimeScale) -``` - -for more see Atom API Docs - -# Documentation - -[API Docs](https://godoc.org/github.com/nareix/mp4) - -[Atom API Docs](https://godoc.org/github.com/nareix/mp4/atom) From 59af05730d45e1e074c5fa33aaea39fe0621c469 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 9 Jun 2016 09:42:28 +0800 Subject: [PATCH 76/93] make IsCodecSupported global --- muxer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/muxer.go b/muxer.go index 7cda260..c6ab707 100644 --- a/muxer.go +++ b/muxer.go @@ -26,7 +26,7 @@ type Muxer struct { mdatWriter *atom.Writer } -func (self *Muxer) IsCodecSupported(codec av.CodecData) bool { +func IsCodecSupported(codec av.CodecData) bool { switch codec.Type() { case av.H264, av.AAC: return true @@ -36,7 +36,7 @@ func (self *Muxer) IsCodecSupported(codec av.CodecData) bool { } func (self *Muxer) NewStream(codec av.CodecData) (err error) { - if !self.IsCodecSupported(codec) { + if !IsCodecSupported(codec) { err = fmt.Errorf("codec type=%x is not supported", codec.Type()) return } From a73846758ae5a45311bbfd1ab4ab74172d1c9402 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 10 Jun 2016 12:14:44 +0800 Subject: [PATCH 77/93] rename frag.go; update genStruct.sh --- atom/{otherStruct.go => frag.go} | 0 atom/genStruct.sh | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) rename atom/{otherStruct.go => frag.go} (100%) diff --git a/atom/otherStruct.go b/atom/frag.go similarity index 100% rename from atom/otherStruct.go rename to atom/frag.go diff --git a/atom/genStruct.sh b/atom/genStruct.sh index eebb079..f8bec11 100755 --- a/atom/genStruct.sh +++ b/atom/genStruct.sh @@ -1,4 +1,7 @@ #!/bin/bash -node --harmony_rest_parameters genStruct.js > struct.go && gofmt -w struct.go && go build . +node genStruct.js > struct.go && gofmt -w struct.go && go build . || { + echo + echo "Please use node version > 6.0.0" +} From 8c611ea1e2452eed8c57cc57511deadc76962a71 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 10 Jun 2016 19:46:00 +0800 Subject: [PATCH 78/93] change error message --- muxer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/muxer.go b/muxer.go index c6ab707..57a04e2 100644 --- a/muxer.go +++ b/muxer.go @@ -145,7 +145,7 @@ func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Media.Info.Sound = &atom.SoundMediaInfo{} } else { - err = fmt.Errorf("please specify stream type") + err = fmt.Errorf("mp4: codec type=%d invalid", self.Type()) } return From 264705818bc35fe3dc0f512d331d8fe98225378c Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 12 Jun 2016 08:52:20 +0800 Subject: [PATCH 79/93] change Streams() --- demuxer.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/demuxer.go b/demuxer.go index e9e1a5e..72fd650 100644 --- a/demuxer.go +++ b/demuxer.go @@ -27,7 +27,11 @@ type Demuxer struct { movieAtom *atom.Movie } -func (self *Demuxer) Streams() (streams []av.CodecData) { +func (self *Demuxer) Streams() (streams []av.CodecData, err error) { + if len(self.streams) == 0 { + err = fmt.Errorf("mp4: no streams") + return + } for _, stream := range self.streams { streams = append(streams, stream) } From 366f7b4dfafe30357464eea58ebd1a8c6ea02fff Mon Sep 17 00:00:00 2001 From: nareix Date: Sun, 12 Jun 2016 14:55:48 +0800 Subject: [PATCH 80/93] add trex,mvex --- atom/atom.go | 44 ++++++++++++ atom/dumper.go | 1 + atom/genStruct.js | 23 ++++++ atom/struct.go | 179 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 244 insertions(+), 3 deletions(-) diff --git a/atom/atom.go b/atom/atom.go index f92cbab..f2551cd 100644 --- a/atom/atom.go +++ b/atom/atom.go @@ -1 +1,45 @@ package atom + +import ( + "io" +) + +func WalkFile(w Walker, r io.Reader) (err error) { + var moov *Movie + var moof *MovieFrag + + for { + var lr *io.LimitedReader + var cc4 string + if lr, cc4, err = ReadAtomHeader(lr, ""); err != nil { + return + } + + switch cc4 { + case "moov": + if moov, err = ReadMovie(lr); err != nil { + return + } + WalkMovie(w, moov) + + case "moof": + if moof, err = ReadMovieFrag(lr); err != nil { + return + } + WalkMovieFrag(w, moof) + + case "mdat": + w.StartStruct("MovieData") + w.Name("Length") + w.Int64(lr.N) + w.EndStruct() + } + + if _, err = ReadDummy(r, int(lr.N)); err != nil { + return + } + } + + return +} + diff --git a/atom/dumper.go b/atom/dumper.go index 262ed13..d77ea0a 100644 --- a/atom/dumper.go +++ b/atom/dumper.go @@ -93,3 +93,4 @@ func (self Dumper) Bytes(val []byte) { func (self Dumper) TimeStamp(val TimeStamp) { self.Println(fmt.Sprintf("%s: %d", self.name, int(val))) } + diff --git a/atom/genStruct.js b/atom/genStruct.js index 53c8b38..ef38ead 100644 --- a/atom/genStruct.js +++ b/atom/genStruct.js @@ -12,6 +12,7 @@ var atoms = { ['header', '*movieHeader'], ['iods', '*iods'], ['tracks', '[]*track'], + ['movieExtend', '*movieExtend'], ]], ], }, @@ -382,6 +383,28 @@ var atoms = { cc4: 'tfhd', }, + movieExtend: { + cc4: 'mvex', + fields: [ + ['$atoms', [ + ['tracks', '[]*trackExtend'], + ]], + ], + }, + + trackExtend: { + cc4: 'trex', + fields: [ + ['version', 'int8'], + ['flags', 'int24'], + ['trackId', 'int32'], + ['defaultSampleDescIdx', 'int32'], + ['defaultSampleDuration', 'int32'], + ['defaultSampleSize', 'int32'], + ['defaultSampleFlags', 'int32'], + ], + }, + /* // need hand write trackFragRun: { diff --git a/atom/struct.go b/atom/struct.go index bc49400..573df51 100644 --- a/atom/struct.go +++ b/atom/struct.go @@ -6,9 +6,10 @@ import ( ) type Movie struct { - Header *MovieHeader - Iods *Iods - Tracks []*Track + Header *MovieHeader + Iods *Iods + Tracks []*Track + MovieExtend *MovieExtend } func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { @@ -41,6 +42,12 @@ func ReadMovie(r *io.LimitedReader) (res *Movie, err error) { } self.Tracks = append(self.Tracks, item) } + case "mvex": + { + if self.MovieExtend, err = ReadMovieExtend(ar); err != nil { + return + } + } } if _, err = ReadDummy(ar, int(ar.N)); err != nil { @@ -74,6 +81,11 @@ func WriteMovie(w io.WriteSeeker, self *Movie) (err error) { } } } + if self.MovieExtend != nil { + if err = WriteMovieExtend(w, self.MovieExtend); err != nil { + return + } + } if err = aw.Close(); err != nil { return } @@ -98,6 +110,9 @@ func WalkMovie(w Walker, self *Movie) { break } } + if self.MovieExtend != nil { + WalkMovieExtend(w, self.MovieExtend) + } w.EndStruct() return } @@ -2699,3 +2714,161 @@ func WalkTrackFrag(w Walker, self *TrackFrag) { w.EndStruct() return } + +type MovieExtend struct { + Tracks []*TrackExtend +} + +func ReadMovieExtend(r *io.LimitedReader) (res *MovieExtend, err error) { + + self := &MovieExtend{} + for r.N > 0 { + var cc4 string + var ar *io.LimitedReader + if ar, cc4, err = ReadAtomHeader(r, ""); err != nil { + return + } + switch cc4 { + case "trex": + { + var item *TrackExtend + if item, err = ReadTrackExtend(ar); err != nil { + return + } + self.Tracks = append(self.Tracks, item) + } + + } + if _, err = ReadDummy(ar, int(ar.N)); err != nil { + return + } + } + res = self + return +} +func WriteMovieExtend(w io.WriteSeeker, self *MovieExtend) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "mvex"); err != nil { + return + } + w = aw + if self.Tracks != nil { + for _, elem := range self.Tracks { + if err = WriteTrackExtend(w, elem); err != nil { + return + } + } + } + if err = aw.Close(); err != nil { + return + } + return +} +func WalkMovieExtend(w Walker, self *MovieExtend) { + + w.StartStruct("MovieExtend") + for i, item := range self.Tracks { + if w.FilterArrayItem("MovieExtend", "Tracks", i, len(self.Tracks)) { + if item != nil { + WalkTrackExtend(w, item) + } + } else { + w.ArrayLeft(i, len(self.Tracks)) + break + } + } + w.EndStruct() + return +} + +type TrackExtend struct { + Version int + Flags int + TrackId int + DefaultSampleDescIdx int + DefaultSampleDuration int + DefaultSampleSize int + DefaultSampleFlags int +} + +func ReadTrackExtend(r *io.LimitedReader) (res *TrackExtend, err error) { + + self := &TrackExtend{} + if self.Version, err = ReadInt(r, 1); err != nil { + return + } + if self.Flags, err = ReadInt(r, 3); err != nil { + return + } + if self.TrackId, err = ReadInt(r, 4); err != nil { + return + } + if self.DefaultSampleDescIdx, err = ReadInt(r, 4); err != nil { + return + } + if self.DefaultSampleDuration, err = ReadInt(r, 4); err != nil { + return + } + if self.DefaultSampleSize, err = ReadInt(r, 4); err != nil { + return + } + if self.DefaultSampleFlags, err = ReadInt(r, 4); err != nil { + return + } + res = self + return +} +func WriteTrackExtend(w io.WriteSeeker, self *TrackExtend) (err error) { + + var aw *Writer + if aw, err = WriteAtomHeader(w, "trex"); err != nil { + return + } + w = aw + if err = WriteInt(w, self.Version, 1); err != nil { + return + } + if err = WriteInt(w, self.Flags, 3); err != nil { + return + } + if err = WriteInt(w, self.TrackId, 4); err != nil { + return + } + if err = WriteInt(w, self.DefaultSampleDescIdx, 4); err != nil { + return + } + if err = WriteInt(w, self.DefaultSampleDuration, 4); err != nil { + return + } + if err = WriteInt(w, self.DefaultSampleSize, 4); err != nil { + return + } + if err = WriteInt(w, self.DefaultSampleFlags, 4); err != nil { + return + } + if err = aw.Close(); err != nil { + return + } + return +} +func WalkTrackExtend(w Walker, self *TrackExtend) { + + w.StartStruct("TrackExtend") + w.Name("Version") + w.Int(self.Version) + w.Name("Flags") + w.Int(self.Flags) + w.Name("TrackId") + w.Int(self.TrackId) + w.Name("DefaultSampleDescIdx") + w.Int(self.DefaultSampleDescIdx) + w.Name("DefaultSampleDuration") + w.Int(self.DefaultSampleDuration) + w.Name("DefaultSampleSize") + w.Int(self.DefaultSampleSize) + w.Name("DefaultSampleFlags") + w.Int(self.DefaultSampleFlags) + w.EndStruct() + return +} From c463a3678f4ad0ae9329bd1cadc9c3e9a4d0e80b Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 18 Jun 2016 08:15:30 +0800 Subject: [PATCH 81/93] change float64->float32 --- atom/atom.go | 1 - atom/dumper.go | 1 - demuxer.go | 12 ++++++------ muxer.go | 8 ++++---- stream.go | 10 +++++----- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/atom/atom.go b/atom/atom.go index f2551cd..70843fc 100644 --- a/atom/atom.go +++ b/atom/atom.go @@ -42,4 +42,3 @@ func WalkFile(w Walker, r io.Reader) (err error) { return } - diff --git a/atom/dumper.go b/atom/dumper.go index d77ea0a..262ed13 100644 --- a/atom/dumper.go +++ b/atom/dumper.go @@ -93,4 +93,3 @@ func (self Dumper) Bytes(val []byte) { func (self Dumper) TimeStamp(val TimeStamp) { self.Println(fmt.Sprintf("%s: %d", self.name, int(val))) } - diff --git a/demuxer.go b/demuxer.go index 72fd650..89c4c1d 100644 --- a/demuxer.go +++ b/demuxer.go @@ -4,10 +4,10 @@ import ( "bytes" "fmt" "github.com/nareix/av" - "github.com/nareix/mp4/atom" - "github.com/nareix/mp4/isom" "github.com/nareix/codec/aacparser" "github.com/nareix/codec/h264parser" + "github.com/nareix/mp4/atom" + "github.com/nareix/mp4/isom" "io" ) @@ -322,7 +322,7 @@ func (self *Demuxer) ReadPacket() (streamIndex int, pkt av.Packet, err error) { return } -func (self *Demuxer) CurrentTime() (time float64) { +func (self *Demuxer) CurrentTime() (time float32) { if len(self.streams) > 0 { stream := self.streams[0] time = stream.tsToTime(stream.dts) @@ -330,7 +330,7 @@ func (self *Demuxer) CurrentTime() (time float64) { return } -func (self *Demuxer) SeekToTime(time float64) (err error) { +func (self *Demuxer) SeekToTime(time float32) (err error) { for _, stream := range self.streams { if stream.IsVideo() { if err = stream.seekToTime(time); err != nil { @@ -395,7 +395,7 @@ func (self *Stream) readPacket() (pkt av.Packet, err error) { return } -func (self *Stream) seekToTime(time float64) (err error) { +func (self *Stream) seekToTime(time float32) (err error) { index := self.timeToSampleIndex(time) if err = self.setSampleIndex(index); err != nil { return @@ -406,7 +406,7 @@ func (self *Stream) seekToTime(time float64) (err error) { return } -func (self *Stream) timeToSampleIndex(time float64) int { +func (self *Stream) timeToSampleIndex(time float32) int { targetTs := self.timeToTs(time) targetIndex := 0 diff --git a/muxer.go b/muxer.go index 57a04e2..4493f1f 100644 --- a/muxer.go +++ b/muxer.go @@ -184,7 +184,7 @@ func (self *Muxer) WritePacket(streamIndex int, pkt av.Packet) (err error) { } newpkt := pkt newpkt.Data = payload - newpkt.Duration = float64(samples)/float64(sampleRate) + newpkt.Duration = float32(samples) / float32(sampleRate) if err = stream.writePacket(newpkt); err != nil { return } @@ -258,21 +258,21 @@ func (self *Muxer) WriteTrailer() (err error) { NextTrackId: 2, } - maxDur := float64(0) + maxDur := float32(0) timeScale := 10000 for _, stream := range self.streams { if err = stream.fillTrackAtom(); err != nil { return } dur := stream.tsToTime(stream.duration) - stream.trackAtom.Header.Duration = int(float64(timeScale) * dur) + stream.trackAtom.Header.Duration = int(float32(timeScale) * dur) if dur > maxDur { maxDur = dur } moov.Tracks = append(moov.Tracks, stream.trackAtom) } moov.Header.TimeScale = timeScale - moov.Header.Duration = int(float64(timeScale) * maxDur) + moov.Header.Duration = int(float32(timeScale) * maxDur) if err = self.mdatWriter.Close(); err != nil { return diff --git a/stream.go b/stream.go index 2e6cdf2..e2544dc 100644 --- a/stream.go +++ b/stream.go @@ -14,7 +14,7 @@ type Stream struct { idx int timeScale int64 - duration int64 + duration int64 muxer *Muxer @@ -39,10 +39,10 @@ type Stream struct { cttsEntry *atom.CompositionOffsetEntry } -func (self *Stream) timeToTs(time float64) int64 { - return int64(time * float64(self.timeScale)) +func (self *Stream) timeToTs(time float32) int64 { + return int64(time * float32(self.timeScale)) } -func (self *Stream) tsToTime(ts int64) float64 { - return float64(ts) / float64(self.timeScale) +func (self *Stream) tsToTime(ts int64) float32 { + return float32(ts) / float32(self.timeScale) } From b255ae33bce3c1e3b2d8abf0c8d8c2cef4b16c3a Mon Sep 17 00:00:00 2001 From: nareix Date: Sat, 18 Jun 2016 10:05:32 +0800 Subject: [PATCH 82/93] switch time.Duration --- demuxer.go | 27 ++++++++++++++------------- muxer.go | 15 ++++++++------- stream.go | 17 +++++++++++++---- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/demuxer.go b/demuxer.go index 89c4c1d..7770df8 100644 --- a/demuxer.go +++ b/demuxer.go @@ -2,6 +2,7 @@ package mp4 import ( "bytes" + "time" "fmt" "github.com/nareix/av" "github.com/nareix/codec/aacparser" @@ -322,28 +323,28 @@ func (self *Demuxer) ReadPacket() (streamIndex int, pkt av.Packet, err error) { return } -func (self *Demuxer) CurrentTime() (time float32) { +func (self *Demuxer) CurrentTime() (tm time.Duration) { if len(self.streams) > 0 { stream := self.streams[0] - time = stream.tsToTime(stream.dts) + tm = stream.tsToTime(stream.dts) } return } -func (self *Demuxer) SeekToTime(time float32) (err error) { +func (self *Demuxer) SeekToTime(tm time.Duration) (err error) { for _, stream := range self.streams { - if stream.IsVideo() { - if err = stream.seekToTime(time); err != nil { + if stream.Type().IsVideo() { + if err = stream.seekToTime(tm); err != nil { return } - time = stream.tsToTime(stream.dts) + tm = stream.tsToTime(stream.dts) break } } for _, stream := range self.streams { - if !stream.IsVideo() { - if err = stream.seekToTime(time); err != nil { + if !stream.Type().IsVideo() { + if err = stream.seekToTime(tm); err != nil { return } } @@ -395,19 +396,19 @@ func (self *Stream) readPacket() (pkt av.Packet, err error) { return } -func (self *Stream) seekToTime(time float32) (err error) { - index := self.timeToSampleIndex(time) +func (self *Stream) seekToTime(tm time.Duration) (err error) { + index := self.timeToSampleIndex(tm) if err = self.setSampleIndex(index); err != nil { return } if false { - fmt.Printf("stream[%d]: seekToTime index=%v time=%v cur=%v\n", self.idx, index, time, self.tsToTime(self.dts)) + fmt.Printf("stream[%d]: seekToTime index=%v time=%v cur=%v\n", self.idx, index, tm, self.tsToTime(self.dts)) } return } -func (self *Stream) timeToSampleIndex(time float32) int { - targetTs := self.timeToTs(time) +func (self *Stream) timeToSampleIndex(tm time.Duration) int { + targetTs := self.timeToTs(tm) targetIndex := 0 startTs := int64(0) diff --git a/muxer.go b/muxer.go index 4493f1f..ab7e801 100644 --- a/muxer.go +++ b/muxer.go @@ -3,6 +3,7 @@ package mp4 import ( "bytes" "fmt" + "time" "github.com/nareix/av" "github.com/nareix/codec/aacparser" "github.com/nareix/codec/h264parser" @@ -162,7 +163,7 @@ func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) { return } for _, stream := range self.streams { - if stream.IsVideo() { + if stream.Type().IsVideo() { stream.sample.CompositionOffset = &atom.CompositionOffset{} } } @@ -184,7 +185,7 @@ func (self *Muxer) WritePacket(streamIndex int, pkt av.Packet) (err error) { } newpkt := pkt newpkt.Data = payload - newpkt.Duration = float32(samples) / float32(sampleRate) + newpkt.Duration = time.Duration(samples)*time.Second / time.Duration(sampleRate) if err = stream.writePacket(newpkt); err != nil { return } @@ -258,21 +259,21 @@ func (self *Muxer) WriteTrailer() (err error) { NextTrackId: 2, } - maxDur := float32(0) - timeScale := 10000 + maxDur := time.Duration(0) + timeScale := int64(10000) for _, stream := range self.streams { if err = stream.fillTrackAtom(); err != nil { return } dur := stream.tsToTime(stream.duration) - stream.trackAtom.Header.Duration = int(float32(timeScale) * dur) + stream.trackAtom.Header.Duration = int(timeToTs(dur, timeScale)) if dur > maxDur { maxDur = dur } moov.Tracks = append(moov.Tracks, stream.trackAtom) } - moov.Header.TimeScale = timeScale - moov.Header.Duration = int(float32(timeScale) * maxDur) + moov.Header.TimeScale = int(timeScale) + moov.Header.Duration = int(timeToTs(maxDur, timeScale)) if err = self.mdatWriter.Close(); err != nil { return diff --git a/stream.go b/stream.go index e2544dc..76f28ba 100644 --- a/stream.go +++ b/stream.go @@ -3,6 +3,7 @@ package mp4 import ( "github.com/nareix/av" "github.com/nareix/mp4/atom" + "time" "io" ) @@ -39,10 +40,18 @@ type Stream struct { cttsEntry *atom.CompositionOffsetEntry } -func (self *Stream) timeToTs(time float32) int64 { - return int64(time * float32(self.timeScale)) +func timeToTs(tm time.Duration, timeScale int64) int64 { + return int64(tm*time.Duration(timeScale) / time.Second) } -func (self *Stream) tsToTime(ts int64) float32 { - return float32(ts) / float32(self.timeScale) +func tsToTime(ts int64, timeScale int64) time.Duration { + return time.Duration(ts)*time.Second / time.Duration(timeScale) +} + +func (self *Stream) timeToTs(tm time.Duration) int64 { + return int64(tm*time.Duration(self.timeScale) / time.Second) +} + +func (self *Stream) tsToTime(ts int64) time.Duration { + return time.Duration(ts)*time.Second / time.Duration(self.timeScale) } From 28055b36cf4a4835b0dffeaf6b001cff420837a0 Mon Sep 17 00:00:00 2001 From: nareix Date: Wed, 22 Jun 2016 17:58:03 +0800 Subject: [PATCH 83/93] remove normalizer --- demuxer.go | 17 +++++++++++------ muxer.go | 49 +++++++++++++++++++++---------------------------- stream.go | 2 ++ 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/demuxer.go b/demuxer.go index 7770df8..268bd49 100644 --- a/demuxer.go +++ b/demuxer.go @@ -34,7 +34,7 @@ func (self *Demuxer) Streams() (streams []av.CodecData, err error) { return } for _, stream := range self.streams { - streams = append(streams, stream) + streams = append(streams, stream.CodecData) } return } @@ -308,18 +308,24 @@ func (self *Stream) sampleCount() int { } } -func (self *Demuxer) ReadPacket() (streamIndex int, pkt av.Packet, err error) { +func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { var chosen *Stream + var chosenidx int for i, stream := range self.streams { if chosen == nil || stream.tsToTime(stream.dts) < chosen.tsToTime(chosen.dts) { chosen = stream - streamIndex = i + chosenidx = i } } if false { fmt.Printf("ReadPacket: chosen index=%v time=%v\n", chosen.idx, chosen.tsToTime(chosen.dts)) } - pkt, err = chosen.readPacket() + tm := chosen.tsToTime(chosen.dts) + if pkt, err = chosen.readPacket(); err != nil { + return + } + pkt.Time = tm + pkt.Idx = int8(chosenidx) return } @@ -390,8 +396,7 @@ func (self *Stream) readPacket() (pkt av.Packet, err error) { pkt.CompositionTime = self.tsToTime(cts) } - duration := self.incSampleIndex() - pkt.Duration = self.tsToTime(duration) + self.incSampleIndex() return } diff --git a/muxer.go b/muxer.go index ab7e801..ddc6615 100644 --- a/muxer.go +++ b/muxer.go @@ -27,7 +27,7 @@ type Muxer struct { mdatWriter *atom.Writer } -func IsCodecSupported(codec av.CodecData) bool { +func (self *Muxer) isCodecSupported(codec av.CodecData) bool { switch codec.Type() { case av.H264, av.AAC: return true @@ -36,8 +36,8 @@ func IsCodecSupported(codec av.CodecData) bool { } } -func (self *Muxer) NewStream(codec av.CodecData) (err error) { - if !IsCodecSupported(codec) { +func (self *Muxer) newStream(codec av.CodecData) (err error) { + if !self.isCodecSupported(codec) { err = fmt.Errorf("codec type=%x is not supported", codec.Type()) return } @@ -153,8 +153,9 @@ func (self *Stream) fillTrackAtom() (err error) { } func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) { + self.streams = []*Stream{} for _, stream := range streams { - if err = self.NewStream(stream); err != nil { + if err = self.newStream(stream); err != nil { return } } @@ -170,34 +171,26 @@ func (self *Muxer) WriteHeader(streams []av.CodecData) (err error) { return } -func (self *Muxer) WritePacket(streamIndex int, pkt av.Packet) (err error) { - stream := self.streams[streamIndex] - frame := pkt.Data - - if stream.Type() == av.AAC && aacparser.IsADTSFrame(frame) { - sampleRate := stream.CodecData.(av.AudioCodecData).SampleRate() - for len(frame) > 0 { - var payload []byte - var samples int - var framelen int - if _, payload, samples, framelen, err = aacparser.ReadADTSFrame(frame); err != nil { - return - } - newpkt := pkt - newpkt.Data = payload - newpkt.Duration = time.Duration(samples)*time.Second / time.Duration(sampleRate) - if err = stream.writePacket(newpkt); err != nil { - return - } - frame = frame[framelen:] - } +func (self *Muxer) WritePacket(pkt av.Packet) (err error) { + stream := self.streams[pkt.Idx] + if err = stream.writePacket(pkt); err != nil { return } - - return stream.writePacket(pkt) + return } func (self *Stream) writePacket(pkt av.Packet) (err error) { + if self.lasttime == 0 { + self.lasttime = pkt.Time + return + } + rawdur := pkt.Time - self.lasttime + if rawdur < 0 { + err = fmt.Errorf("mp4: stream#%d time=%v < lasttime=%v", pkt.Idx, pkt.Time, self.lasttime) + return + } + self.lasttime = pkt.Time + var filePos int64 var sampleSize int @@ -225,7 +218,7 @@ func (self *Stream) writePacket(pkt av.Packet) (err error) { self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, self.sampleIndex+1) } - duration := int(self.timeToTs(pkt.Duration)) + duration := int(self.timeToTs(rawdur)) if self.sttsEntry == nil || duration != self.sttsEntry.Duration { self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, atom.TimeToSampleEntry{Duration: duration}) self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1] diff --git a/stream.go b/stream.go index 76f28ba..e99ef64 100644 --- a/stream.go +++ b/stream.go @@ -14,6 +14,8 @@ type Stream struct { r io.ReadSeeker idx int + lasttime time.Duration + timeScale int64 duration int64 From d0d400454d738273e841374db864421bf4ec8adf Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 23 Jun 2016 19:02:02 +0800 Subject: [PATCH 84/93] modify comment --- muxer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/muxer.go b/muxer.go index ddc6615..7e3c6b7 100644 --- a/muxer.go +++ b/muxer.go @@ -38,7 +38,7 @@ func (self *Muxer) isCodecSupported(codec av.CodecData) bool { func (self *Muxer) newStream(codec av.CodecData) (err error) { if !self.isCodecSupported(codec) { - err = fmt.Errorf("codec type=%x is not supported", codec.Type()) + err = fmt.Errorf("mp4: codec type=%v is not supported", codec.Type()) return } From fd62ece67dcaccffe62fb3e6c5ec4a34fe435063 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 19:42:15 +0800 Subject: [PATCH 85/93] add handler --- demuxer.go | 9 --------- handler.go | 18 ++++++++++++++++++ muxer.go | 9 --------- 3 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 handler.go diff --git a/demuxer.go b/demuxer.go index 268bd49..0f24e8d 100644 --- a/demuxer.go +++ b/demuxer.go @@ -12,15 +12,6 @@ import ( "io" ) -func Open(R io.ReadSeeker) (demuxer *Demuxer, err error) { - _demuxer := &Demuxer{R: R} - if err = _demuxer.ReadHeader(); err != nil { - return - } - demuxer = _demuxer - return -} - type Demuxer struct { R io.ReadSeeker diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..a44a998 --- /dev/null +++ b/handler.go @@ -0,0 +1,18 @@ +package mp4 + +import ( + "io" + "github.com/nareix/av" + "github.com/nareix/av/avutil" +) + +func Handler(h *avutil.RegisterHandler) { + h.Ext = ".ts" + h.ReaderDemuxer = func(r io.Reader) av.Demuxer { + return &Demuxer{R: r.(io.ReadSeeker)} + } + h.WriterMuxer = func(w io.Writer) av.Muxer { + return &Muxer{W: w.(io.WriteSeeker)} + } +} + diff --git a/muxer.go b/muxer.go index 7e3c6b7..72c10b1 100644 --- a/muxer.go +++ b/muxer.go @@ -12,15 +12,6 @@ import ( "io" ) -func Create(W io.WriteSeeker, streams []av.CodecData) (muxer *Muxer, err error) { - _muxer := &Muxer{W: W} - if err = _muxer.WriteHeader(streams); err != nil { - return - } - muxer = _muxer - return -} - type Muxer struct { W io.WriteSeeker streams []*Stream From 4a10a9ef1b5013e160c433fd6ea296bafc8d158b Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 22:55:31 +0800 Subject: [PATCH 86/93] ReadHeader() -> Streams() --- demuxer.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/demuxer.go b/demuxer.go index 0f24e8d..ef3a8f2 100644 --- a/demuxer.go +++ b/demuxer.go @@ -20,8 +20,7 @@ type Demuxer struct { } func (self *Demuxer) Streams() (streams []av.CodecData, err error) { - if len(self.streams) == 0 { - err = fmt.Errorf("mp4: no streams") + if err = self.probe(); err != nil { return } for _, stream := range self.streams { @@ -30,7 +29,11 @@ func (self *Demuxer) Streams() (streams []av.CodecData, err error) { return } -func (self *Demuxer) ReadHeader() (err error) { +func (self *Demuxer) probe() (err error) { + if self.movieAtom != nil { + return + } + var N int64 var moov *atom.Movie @@ -65,7 +68,6 @@ func (self *Demuxer) ReadHeader() (err error) { err = fmt.Errorf("'moov' atom not found") return } - self.movieAtom = moov self.streams = []*Stream{} for i, atrack := range moov.Tracks { @@ -101,6 +103,7 @@ func (self *Demuxer) ReadHeader() (err error) { } } + self.movieAtom = moov return } @@ -300,6 +303,10 @@ func (self *Stream) sampleCount() int { } func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { + if err = self.probe(); err != nil { + return + } + var chosen *Stream var chosenidx int for i, stream := range self.streams { From 9f0741d706414c7779bbdab2798714a9aa0bdb65 Mon Sep 17 00:00:00 2001 From: nareix Date: Thu, 30 Jun 2016 22:57:21 +0800 Subject: [PATCH 87/93] small mistake --- handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handler.go b/handler.go index a44a998..1734524 100644 --- a/handler.go +++ b/handler.go @@ -7,7 +7,7 @@ import ( ) func Handler(h *avutil.RegisterHandler) { - h.Ext = ".ts" + h.Ext = ".mp4" h.ReaderDemuxer = func(r io.Reader) av.Demuxer { return &Demuxer{R: r.(io.ReadSeeker)} } From 6ceb48ab20d069ac08b506ce147d40386573f592 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 18:17:37 +0800 Subject: [PATCH 88/93] muxer: use h264parser.CheckNALUsType instead of SplitNALUs --- muxer.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/muxer.go b/muxer.go index 72c10b1..70ef9f2 100644 --- a/muxer.go +++ b/muxer.go @@ -5,6 +5,7 @@ import ( "fmt" "time" "github.com/nareix/av" + "github.com/nareix/pio" "github.com/nareix/codec/aacparser" "github.com/nareix/codec/h264parser" "github.com/nareix/mp4/atom" @@ -190,12 +191,17 @@ func (self *Stream) writePacket(pkt av.Packet) (err error) { } if self.Type() == av.H264 { - nalus, _ := h264parser.SplitNALUs(pkt.Data) - h264parser.WalkNALUsAVCC(nalus, func(b []byte) { - sampleSize += len(b) - _, err = self.muxer.mdatWriter.Write(b) - }) - if err != nil { + if typ := h264parser.CheckNALUsType(pkt.Data); typ != h264parser.NALU_RAW { + err = fmt.Errorf("mp4: nalu format=%d is not raw", typ) + return + } + var b [4]byte + pio.PutU32BE(b[:], uint32(len(pkt.Data))) + sampleSize += len(pkt.Data)+4 + if _, err = self.muxer.mdatWriter.Write(b[:]); err != nil { + return + } + if _, err = self.muxer.mdatWriter.Write(pkt.Data); err != nil { return } } else { From 8c0225de8259be18cfcbb38f4211ac9d20b54243 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 18:22:33 +0800 Subject: [PATCH 89/93] demuxer: check nalu type --- demuxer.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/demuxer.go b/demuxer.go index ef3a8f2..2bf5918 100644 --- a/demuxer.go +++ b/demuxer.go @@ -382,6 +382,15 @@ func (self *Stream) readPacket() (pkt av.Packet, err error) { return } + switch self.Type() { + case av.H264: + if typ := h264parser.CheckNALUsType(pkt.Data); typ != h264parser.NALU_AVCC { + err = fmt.Errorf("mp4: demuxer: h264 nalu format=%d invalid", typ) + return + } + pkt.Data = pkt.Data[4:] + } + if self.sample.SyncSample != nil { if self.sample.SyncSample.Entries[self.syncSampleIndex]-1 == self.sampleIndex { pkt.IsKeyFrame = true From 4e5fa6d414b5e4936edd6559429c5b5224026d65 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 19:20:29 +0800 Subject: [PATCH 90/93] change to h264parser.FindDataNALUInAVCCNALUs() --- demuxer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demuxer.go b/demuxer.go index 2bf5918..bce0ebf 100644 --- a/demuxer.go +++ b/demuxer.go @@ -384,11 +384,11 @@ func (self *Stream) readPacket() (pkt av.Packet, err error) { switch self.Type() { case av.H264: - if typ := h264parser.CheckNALUsType(pkt.Data); typ != h264parser.NALU_AVCC { - err = fmt.Errorf("mp4: demuxer: h264 nalu format=%d invalid", typ) + var ok bool + if pkt.Data, ok = h264parser.FindDataNALUInAVCCNALUs(pkt.Data); !ok { + err = fmt.Errorf("rtmp: input h264 format invalid") return } - pkt.Data = pkt.Data[4:] } if self.sample.SyncSample != nil { From 647db1ed5c971bedb0db1d62f82f97c7098c8672 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 19:26:31 +0800 Subject: [PATCH 91/93] spell error --- demuxer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demuxer.go b/demuxer.go index bce0ebf..3a96340 100644 --- a/demuxer.go +++ b/demuxer.go @@ -386,7 +386,7 @@ func (self *Stream) readPacket() (pkt av.Packet, err error) { case av.H264: var ok bool if pkt.Data, ok = h264parser.FindDataNALUInAVCCNALUs(pkt.Data); !ok { - err = fmt.Errorf("rtmp: input h264 format invalid") + err = fmt.Errorf("mp4: input h264 format invalid") return } } From 9965a4c7685e468cc6eb8d004d127917896f2570 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 19:51:55 +0800 Subject: [PATCH 92/93] add error msg --- demuxer.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/demuxer.go b/demuxer.go index 3a96340..f606c12 100644 --- a/demuxer.go +++ b/demuxer.go @@ -65,7 +65,7 @@ func (self *Demuxer) probe() (err error) { } if moov == nil { - err = fmt.Errorf("'moov' atom not found") + err = fmt.Errorf("mp4: 'moov' atom not found") return } @@ -80,7 +80,7 @@ func (self *Demuxer) probe() (err error) { stream.sample = atrack.Media.Info.Sample stream.timeScale = int64(atrack.Media.Header.TimeScale) } else { - err = fmt.Errorf("sample table not found") + err = fmt.Errorf("mp4: sample table not found") return } @@ -126,7 +126,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { start += n } if !found { - err = fmt.Errorf("stream[%d]: cannot locate sample index in chunk", self.idx) + err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in chunk", self.idx) return } @@ -134,7 +134,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { self.sampleOffsetInChunk = int64(self.sampleIndexInChunk * self.sample.SampleSize.SampleSize) } else { if index >= len(self.sample.SampleSize.Entries) { - err = fmt.Errorf("stream[%d]: sample index out of range", self.idx) + err = fmt.Errorf("mp4: stream[%d]: sample index out of range", self.idx) return } self.sampleOffsetInChunk = int64(0) @@ -161,7 +161,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { self.sttsEntryIndex++ } if !found { - err = fmt.Errorf("stream[%d]: cannot locate sample index in stts entry", self.idx) + err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in stts entry", self.idx) return } @@ -180,7 +180,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { self.cttsEntryIndex++ } if !found { - err = fmt.Errorf("stream[%d]: cannot locate sample index in ctts entry", self.idx) + err = fmt.Errorf("mp4: stream[%d]: cannot locate sample index in ctts entry", self.idx) return } } @@ -196,7 +196,7 @@ func (self *Stream) setSampleIndex(index int) (err error) { } if false { - fmt.Printf("stream[%d]: setSampleIndex chunkGroupIndex=%d chunkIndex=%d sampleOffsetInChunk=%d\n", + fmt.Printf("mp4: stream[%d]: setSampleIndex chunkGroupIndex=%d chunkIndex=%d sampleOffsetInChunk=%d\n", self.idx, self.chunkGroupIndex, self.chunkIndex, self.sampleOffsetInChunk) } From bd71ca9823ec91410ccdf4d2ed783ba44b8a14d7 Mon Sep 17 00:00:00 2001 From: nareix Date: Fri, 1 Jul 2016 19:53:36 +0800 Subject: [PATCH 93/93] bugfix: if h264 create sync sample --- muxer.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/muxer.go b/muxer.go index 70ef9f2..643f25c 100644 --- a/muxer.go +++ b/muxer.go @@ -78,6 +78,11 @@ func (self *Muxer) newStream(codec av.CodecData) (err error) { }, } + switch codec.Type() { + case av.H264: + stream.sample.SyncSample = &atom.SyncSample{} + } + stream.timeScale = 90000 stream.muxer = self self.streams = append(self.streams, stream) @@ -103,7 +108,6 @@ func (self *Stream) fillTrackAtom() (err error) { ColorTableId: -1, Conf: &atom.Avc1Conf{Data: codec.AVCDecoderConfRecordBytes()}, } - self.sample.SyncSample = &atom.SyncSample{} self.trackAtom.Media.Handler = &atom.HandlerRefer{ SubType: "vide", Name: "Video Media Handler",