add SimpleH264Writer
This commit is contained in:
parent
2ba72a94e8
commit
5b9bccba88
@ -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'],
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
17
avcc.go
@ -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()
|
||||
|
@ -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
494
mp4.go
@ -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
15
mp4a.go
@ -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
156
writer.go
Normal 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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user