add SimpleH264Writer

This commit is contained in:
nareix 2015-11-25 00:01:26 +08:00
parent 2ba72a94e8
commit 5b9bccba88
10 changed files with 213 additions and 563 deletions

View File

@ -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'],

View File

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

View File

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

View File

@ -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 {

View File

@ -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) {

17
avcc.go
View File

@ -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()

View File

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

494
mp4.go
View File

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

15
mp4a.go
View File

@ -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()

156
writer.go Normal file
View File

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