add SimpleH264Writer
This commit is contained in:
parent
2ba72a94e8
commit
5b9bccba88
@ -22,10 +22,10 @@ var atoms = {
|
|||||||
['flags', 'int24'],
|
['flags', 'int24'],
|
||||||
['createTime', 'TimeStamp32'],
|
['createTime', 'TimeStamp32'],
|
||||||
['modifyTime', 'TimeStamp32'],
|
['modifyTime', 'TimeStamp32'],
|
||||||
['timeScale', 'TimeStamp32'],
|
['timeScale', 'int32'],
|
||||||
['duration', 'TimeStamp32'],
|
['duration', 'int32'],
|
||||||
['preferredRate', 'int32'],
|
['preferredRate', 'Fixed32'],
|
||||||
['preferredVolume', 'int16'],
|
['preferredVolume', 'Fixed16'],
|
||||||
['_', '[10]byte'],
|
['_', '[10]byte'],
|
||||||
['matrix', '[9]int32'],
|
['matrix', '[9]int32'],
|
||||||
['previewTime', 'TimeStamp32'],
|
['previewTime', 'TimeStamp32'],
|
||||||
@ -57,11 +57,11 @@ var atoms = {
|
|||||||
['modifyTime', 'TimeStamp32'],
|
['modifyTime', 'TimeStamp32'],
|
||||||
['trackId', 'int32'],
|
['trackId', 'int32'],
|
||||||
['_', '[4]byte'],
|
['_', '[4]byte'],
|
||||||
['duration', 'TimeStamp32'],
|
['duration', 'int32'],
|
||||||
['_', '[8]byte'],
|
['_', '[8]byte'],
|
||||||
['layer', 'int16'],
|
['layer', 'int16'],
|
||||||
['alternateGroup', 'int16'],
|
['alternateGroup', 'int16'],
|
||||||
['volume', 'int16'],
|
['volume', 'Fixed16'],
|
||||||
['_', '[2]byte'],
|
['_', '[2]byte'],
|
||||||
['matrix', '[9]int32'],
|
['matrix', '[9]int32'],
|
||||||
['trackWidth', 'Fixed32'],
|
['trackWidth', 'Fixed32'],
|
||||||
@ -96,8 +96,8 @@ var atoms = {
|
|||||||
fields: [
|
fields: [
|
||||||
['version', 'int8'],
|
['version', 'int8'],
|
||||||
['flags', 'int24'],
|
['flags', 'int24'],
|
||||||
['createTime', 'int32'],
|
['createTime', 'TimeStamp32'],
|
||||||
['modifyTime', 'int32'],
|
['modifyTime', 'TimeStamp32'],
|
||||||
['timeScale', 'int32'],
|
['timeScale', 'int32'],
|
||||||
['duration', 'int32'],
|
['duration', 'int32'],
|
||||||
['language', 'int16'],
|
['language', 'int16'],
|
||||||
|
@ -20,11 +20,13 @@ func CreateAVCDecoderConfRecord(
|
|||||||
PictureParamSet []byte,
|
PictureParamSet []byte,
|
||||||
) (self AVCDecoderConfRecord, err error) {
|
) (self AVCDecoderConfRecord, err error) {
|
||||||
if len(SeqenceParamSet) < 4 {
|
if len(SeqenceParamSet) < 4 {
|
||||||
err = fmt.Errorf("invalid SeqenceParamSet")
|
err = fmt.Errorf("invalid SeqenceParamSet data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.AVCProfileIndication = int(SeqenceParamSet[1])
|
self.AVCProfileIndication = int(SeqenceParamSet[1])
|
||||||
self.AVCLevelIndication = int(SeqenceParamSet[3])
|
self.AVCLevelIndication = int(SeqenceParamSet[3])
|
||||||
|
self.SeqenceParamSet = [][]byte{SeqenceParamSet}
|
||||||
|
self.PictureParamSet = [][]byte{PictureParamSet}
|
||||||
self.LengthSizeMinusOne = 3
|
self.LengthSizeMinusOne = 3
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,15 @@ func ReadFixed(r io.Reader, n int) (res Fixed, err error) {
|
|||||||
if ui, err = ReadUInt(r, n); err != nil {
|
if ui, err = ReadUInt(r, n); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if n == 2 {
|
||||||
|
res = Fixed(ui<<8)
|
||||||
|
} else if n == 4 {
|
||||||
res = Fixed(ui)
|
res = Fixed(ui)
|
||||||
|
} else {
|
||||||
|
panic("only fixed32 and fixed16 is supported")
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +73,10 @@ type MovieHeader struct {
|
|||||||
Flags int
|
Flags int
|
||||||
CreateTime TimeStamp
|
CreateTime TimeStamp
|
||||||
ModifyTime TimeStamp
|
ModifyTime TimeStamp
|
||||||
TimeScale TimeStamp
|
TimeScale int
|
||||||
Duration TimeStamp
|
Duration int
|
||||||
PreferredRate int
|
PreferredRate Fixed
|
||||||
PreferredVolume int
|
PreferredVolume Fixed
|
||||||
Matrix [9]int
|
Matrix [9]int
|
||||||
PreviewTime TimeStamp
|
PreviewTime TimeStamp
|
||||||
PreviewDuration 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 {
|
if self.ModifyTime, err = ReadTimeStamp(r, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.TimeScale, err = ReadTimeStamp(r, 4); err != nil {
|
if self.TimeScale, err = ReadInt(r, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.Duration, err = ReadTimeStamp(r, 4); err != nil {
|
if self.Duration, err = ReadInt(r, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.PreferredRate, err = ReadInt(r, 4); err != nil {
|
if self.PreferredRate, err = ReadFixed(r, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.PreferredVolume, err = ReadInt(r, 2); err != nil {
|
if self.PreferredVolume, err = ReadFixed(r, 2); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err = ReadDummy(r, 10); err != nil {
|
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 {
|
if err = WriteTimeStamp(w, self.ModifyTime, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteTimeStamp(w, self.TimeScale, 4); err != nil {
|
if err = WriteInt(w, self.TimeScale, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteTimeStamp(w, self.Duration, 4); err != nil {
|
if err = WriteInt(w, self.Duration, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteInt(w, self.PreferredRate, 4); err != nil {
|
if err = WriteFixed(w, self.PreferredRate, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteInt(w, self.PreferredVolume, 2); err != nil {
|
if err = WriteFixed(w, self.PreferredVolume, 2); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteDummy(w, 10); err != nil {
|
if err = WriteDummy(w, 10); err != nil {
|
||||||
@ -277,10 +277,10 @@ type TrackHeader struct {
|
|||||||
CreateTime TimeStamp
|
CreateTime TimeStamp
|
||||||
ModifyTime TimeStamp
|
ModifyTime TimeStamp
|
||||||
TrackId int
|
TrackId int
|
||||||
Duration TimeStamp
|
Duration int
|
||||||
Layer int
|
Layer int
|
||||||
AlternateGroup int
|
AlternateGroup int
|
||||||
Volume int
|
Volume Fixed
|
||||||
Matrix [9]int
|
Matrix [9]int
|
||||||
TrackWidth Fixed
|
TrackWidth Fixed
|
||||||
TrackHeight Fixed
|
TrackHeight Fixed
|
||||||
@ -307,7 +307,7 @@ func ReadTrackHeader(r *io.LimitedReader) (res *TrackHeader, err error) {
|
|||||||
if _, err = ReadDummy(r, 4); err != nil {
|
if _, err = ReadDummy(r, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.Duration, err = ReadTimeStamp(r, 4); err != nil {
|
if self.Duration, err = ReadInt(r, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err = ReadDummy(r, 8); err != nil {
|
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 {
|
if self.AlternateGroup, err = ReadInt(r, 2); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.Volume, err = ReadInt(r, 2); err != nil {
|
if self.Volume, err = ReadFixed(r, 2); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err = ReadDummy(r, 2); err != nil {
|
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 {
|
if err = WriteDummy(w, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteTimeStamp(w, self.Duration, 4); err != nil {
|
if err = WriteInt(w, self.Duration, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteDummy(w, 8); err != nil {
|
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 {
|
if err = WriteInt(w, self.AlternateGroup, 2); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteInt(w, self.Volume, 2); err != nil {
|
if err = WriteFixed(w, self.Volume, 2); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteDummy(w, 2); err != nil {
|
if err = WriteDummy(w, 2); err != nil {
|
||||||
@ -530,8 +530,8 @@ func WriteMedia(w io.WriteSeeker, self *Media) (err error) {
|
|||||||
type MediaHeader struct {
|
type MediaHeader struct {
|
||||||
Version int
|
Version int
|
||||||
Flags int
|
Flags int
|
||||||
CreateTime int
|
CreateTime TimeStamp
|
||||||
ModifyTime int
|
ModifyTime TimeStamp
|
||||||
TimeScale int
|
TimeScale int
|
||||||
Duration int
|
Duration int
|
||||||
Language 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 {
|
if self.Flags, err = ReadInt(r, 3); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.CreateTime, err = ReadInt(r, 4); err != nil {
|
if self.CreateTime, err = ReadTimeStamp(r, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.ModifyTime, err = ReadInt(r, 4); err != nil {
|
if self.ModifyTime, err = ReadTimeStamp(r, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.TimeScale, err = ReadInt(r, 4); err != nil {
|
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 {
|
if err = WriteInt(w, self.Flags, 3); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteInt(w, self.CreateTime, 4); err != nil {
|
if err = WriteTimeStamp(w, self.CreateTime, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteInt(w, self.ModifyTime, 4); err != nil {
|
if err = WriteTimeStamp(w, self.ModifyTime, 4); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = WriteInt(w, self.TimeScale, 4); err != nil {
|
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) {
|
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) {
|
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()
|
flag.Parse()
|
||||||
|
|
||||||
if *testconv {
|
if *testconv {
|
||||||
if _, err := mp4.TestConvert(flag.Arg(0)); err != nil {
|
if err := mp4.TestConvert(flag.Arg(0)); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *testrewrite {
|
if *testrewrite {
|
||||||
if _, err := mp4.TestRewrite(flag.Arg(0)); err != nil {
|
if err := mp4.TestRewrite(flag.Arg(0)); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
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