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 +} +