diff --git a/atom/atom.go b/atom/atom.go index b97bac0..f92cbab 100644 --- a/atom/atom.go +++ b/atom/atom.go @@ -1,3 +1 @@ - package atom - diff --git a/atom/dumper.go b/atom/dumper.go index 3f28e56..262ed13 100644 --- a/atom/dumper.go +++ b/atom/dumper.go @@ -1,16 +1,15 @@ - package atom import ( - "io" - "fmt" - "strings" "encoding/hex" + "fmt" + "io" + "strings" ) type Walker interface { - FilterArrayItem(string,string,int,int) bool - ArrayLeft(int,int) + FilterArrayItem(string, string, int, int) bool + ArrayLeft(int, int) StartStruct(string) EndStruct() Name(string) @@ -25,9 +24,9 @@ type Walker interface { } type Dumper struct { - W io.Writer - depth int - name string + W io.Writer + depth int + name string arrlen int arridx int } @@ -94,4 +93,3 @@ func (self Dumper) Bytes(val []byte) { func (self Dumper) TimeStamp(val TimeStamp) { self.Println(fmt.Sprintf("%s: %d", self.name, int(val))) } - diff --git a/atom/otherStruct.go b/atom/otherStruct.go index 77e6093..2c5d154 100644 --- a/atom/otherStruct.go +++ b/atom/otherStruct.go @@ -3,8 +3,8 @@ package atom import ( "bytes" "fmt" - "io" "github.com/nareix/bits" + "io" ) type H264SPSInfo struct { @@ -363,14 +363,14 @@ const ( ) type TrackFragHeader struct { - Version int - Flags int - Id int - DefaultSize int - DefaultDuration int - DefaultFlags int - BaseDataOffset int64 - StsdId int + Version int + Flags int + Id int + DefaultSize int + DefaultDuration int + DefaultFlags int + BaseDataOffset int64 + StsdId int } func WalkTrackFragHeader(w Walker, self *TrackFragHeader) { @@ -431,7 +431,7 @@ func ReadTrackFragHeader(r *io.LimitedReader) (res *TrackFragHeader, err error) } if self.Flags&TFHD_DEFAULT_FLAGS != 0 { - if self.DefaultFlags,err = ReadInt(r, 4); err != nil { + if self.DefaultFlags, err = ReadInt(r, 4); err != nil { return } } @@ -617,4 +617,3 @@ func WalkTrackFragDecodeTime(w Walker, self *TrackFragDecodeTime) { w.EndStruct() return } - diff --git a/atom/reader.go b/atom/reader.go index afaea0a..b439bab 100644 --- a/atom/reader.go +++ b/atom/reader.go @@ -1,4 +1,3 @@ - package atom import ( @@ -33,7 +32,7 @@ func ReadInt(r io.Reader, n int) (res int, err error) { return } if uval&(1<>16) + return int(val >> 16) } - diff --git a/atom/utils.go b/atom/utils.go index 5e7e792..3f16f8b 100644 --- a/atom/utils.go +++ b/atom/utils.go @@ -1,8 +1,7 @@ - package atom -func GetAVCDecoderConfRecordByTrack(track *Track) (record *AVCDecoderConfRecord) { - if media := track.Media; media != nil { +func GetAVCDecoderConfRecordByTrack(stream *Track) (record *AVCDecoderConfRecord) { + if media := stream.Media; media != nil { if info := media.Info; info != nil { if sample := info.Sample; sample != nil { if desc := sample.SampleDesc; desc != nil { @@ -18,8 +17,8 @@ func GetAVCDecoderConfRecordByTrack(track *Track) (record *AVCDecoderConfRecord) return } -func GetMp4aDescByTrack(track *Track) (mp4a *Mp4aDesc) { - if media := track.Media; media != nil { +func GetMp4aDescByTrack(stream *Track) (mp4a *Mp4aDesc) { + if media := stream.Media; media != nil { if info := media.Info; info != nil { if sample := info.Sample; sample != nil { if desc := sample.SampleDesc; desc != nil { @@ -32,4 +31,3 @@ func GetMp4aDescByTrack(track *Track) (mp4a *Mp4aDesc) { } return } - diff --git a/atom/writer.go b/atom/writer.go index 6ddbc96..0fd08cc 100644 --- a/atom/writer.go +++ b/atom/writer.go @@ -1,4 +1,3 @@ - package atom import ( @@ -16,7 +15,7 @@ func WriteBytes(w io.Writer, b []byte, n int) (err error) { func WriteUInt(w io.Writer, val uint, n int) (err error) { var b [8]byte - for i := n-1; i >= 0; i-- { + for i := n - 1; i >= 0; i-- { b[i] = byte(val) val >>= 8 } @@ -26,7 +25,7 @@ func WriteUInt(w io.Writer, val uint, n int) (err error) { func WriteInt(w io.Writer, val int, n int) (err error) { var uval uint if val < 0 { - uval = uint((1<>8 + uval = uint(val) >> 8 } else if n == 4 { uval = uint(val) } else { @@ -96,7 +95,7 @@ func (self *Writer) Close() (err error) { if curPos, err = self.Seek(0, 1); err != nil { return } - if err = RefillInt(self, self.sizePos, int(curPos - self.sizePos), 4); err != nil { + if err = RefillInt(self, self.sizePos, int(curPos-self.sizePos), 4); err != nil { return } if false { @@ -118,4 +117,3 @@ func WriteAtomHeader(w io.WriteSeeker, cc4 string) (res *Writer, err error) { res = self return } - diff --git a/demuxer.go b/demuxer.go index 7d1b6df..315c798 100644 --- a/demuxer.go +++ b/demuxer.go @@ -1,20 +1,19 @@ - package mp4 import ( - "github.com/nareix/mp4/atom" - "github.com/nareix/mp4/isom" "bytes" "fmt" + "github.com/nareix/av" + "github.com/nareix/mp4/atom" + "github.com/nareix/mp4/isom" "io" ) type Demuxer struct { R io.ReadSeeker - Tracks []*Track - TrackH264 *Track - TrackAAC *Track - MovieAtom *atom.Movie + + streams []*Stream + movieAtom *atom.Movie } func (self *Demuxer) ReadHeader() (err error) { @@ -52,36 +51,35 @@ func (self *Demuxer) ReadHeader() (err error) { err = fmt.Errorf("'moov' atom not found") return } - self.MovieAtom = moov + self.movieAtom = moov - self.Tracks = []*Track{} - for _, atrack := range(moov.Tracks) { - track := &Track{ - TrackAtom: atrack, - r: self.R, + self.streams = []*Stream{} + for _, atrack := range moov.Tracks { + stream := &Stream{ + trackAtom: atrack, + r: self.R, } if atrack.Media != nil && atrack.Media.Info != nil && atrack.Media.Info.Sample != nil { - track.sample = atrack.Media.Info.Sample + stream.sample = atrack.Media.Info.Sample + stream.SetTimeScale(int64(atrack.Media.Header.TimeScale)) } else { err = fmt.Errorf("sample table not found") return } if record := atom.GetAVCDecoderConfRecordByTrack(atrack); record != nil { if len(record.PPS) > 0 { - track.pps = record.PPS[0] + stream.pps = record.PPS[0] } if len(record.SPS) > 0 { - track.sps = record.SPS[0] + stream.sps = record.SPS[0] } - track.Type = H264 - self.TrackH264 = track - self.Tracks = append(self.Tracks, track) + stream.SetType(av.H264) + self.streams = append(self.streams, stream) } else if mp4a := atom.GetMp4aDescByTrack(atrack); mp4a != nil && mp4a.Conf != nil { if config, err := isom.ReadElemStreamDescAAC(bytes.NewReader(mp4a.Conf.Data)); err == nil { - track.mpeg4AudioConfig = config.Complete() - track.Type = AAC - self.TrackAAC = track - self.Tracks = append(self.Tracks, track) + stream.mpeg4AudioConfig = config.Complete() + stream.SetType(av.AAC) + self.streams = append(self.streams, stream) } } } @@ -89,16 +87,16 @@ func (self *Demuxer) ReadHeader() (err error) { return } -func (self *Track) setSampleIndex(index int) (err error) { +func (self *Stream) setSampleIndex(index int) (err error) { found := false start := 0 self.chunkGroupIndex = 0 - for self.chunkIndex = range(self.sample.ChunkOffset.Entries) { + for self.chunkIndex = range self.sample.ChunkOffset.Entries { n := self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk if index >= start && index < start+n { found = true - self.sampleIndexInChunk = index-start + self.sampleIndexInChunk = index - start break } start += n @@ -113,14 +111,14 @@ func (self *Track) setSampleIndex(index int) (err error) { } if self.sample.SampleSize.SampleSize != 0 { - self.sampleOffsetInChunk = int64(self.sampleIndexInChunk*self.sample.SampleSize.SampleSize) + self.sampleOffsetInChunk = int64(self.sampleIndexInChunk * self.sample.SampleSize.SampleSize) } else { if index >= len(self.sample.SampleSize.Entries) { err = io.EOF return } self.sampleOffsetInChunk = int64(0) - for i := index-self.sampleIndexInChunk; i < index; i++ { + for i := index - self.sampleIndexInChunk; i < index; i++ { self.sampleOffsetInChunk += int64(self.sample.SampleSize.Entries[i]) } } @@ -133,12 +131,12 @@ func (self *Track) setSampleIndex(index int) (err error) { entry := self.sample.TimeToSample.Entries[self.sttsEntryIndex] n := entry.Count if index >= start && index < start+n { - self.sampleIndexInSttsEntry = index-start - self.dts += int64((index-start)*entry.Duration) + self.sampleIndexInSttsEntry = index - start + self.dts += int64((index - start) * entry.Duration) break } start += n - self.dts += int64(n*entry.Duration) + self.dts += int64(n * entry.Duration) self.sttsEntryIndex++ } if !found { @@ -153,7 +151,7 @@ func (self *Track) setSampleIndex(index int) (err error) { for self.cttsEntryIndex < len(self.sample.CompositionOffset.Entries) { n := self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Count if index >= start && index < start+n { - self.sampleIndexInCttsEntry = index-start + self.sampleIndexInCttsEntry = index - start break } start += n @@ -179,7 +177,7 @@ func (self *Track) setSampleIndex(index int) (err error) { return } -func (self *Track) isSampleValid() bool { +func (self *Stream) isSampleValid() bool { if self.chunkIndex >= len(self.sample.ChunkOffset.Entries) { return false } @@ -207,7 +205,7 @@ func (self *Track) isSampleValid() bool { return true } -func (self *Track) incSampleIndex() { +func (self *Stream) incSampleIndex() { self.sampleIndexInChunk++ if self.sampleIndexInChunk == self.sample.SampleToChunk.Entries[self.chunkGroupIndex].SamplesPerChunk { self.chunkIndex++ @@ -252,11 +250,11 @@ func (self *Track) incSampleIndex() { self.sampleIndex++ } -func (self *Track) SampleCount() int { +func (self *Stream) SampleCount() int { if self.sample.SampleSize.SampleSize == 0 { chunkGroupIndex := 0 count := 0 - for chunkIndex := range(self.sample.ChunkOffset.Entries) { + for chunkIndex := range self.sample.ChunkOffset.Entries { n := self.sample.SampleToChunk.Entries[chunkGroupIndex].SamplesPerChunk count += n if chunkGroupIndex+1 < len(self.sample.SampleToChunk.Entries) && @@ -270,7 +268,11 @@ func (self *Track) SampleCount() int { } } -func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { +func (self *Demuxer) ReadPacket() (pkt av.Packet, err error) { + return +} + +func (self *Stream) readSample() (pts int64, dts int64, isKeyFrame bool, data []byte, err error) { if !self.isSampleValid() { err = io.EOF return @@ -284,7 +286,7 @@ func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []b sampleSize = self.sample.SampleSize.Entries[self.sampleIndex] } - sampleOffset := int64(chunkOffset)+self.sampleOffsetInChunk + sampleOffset := int64(chunkOffset) + self.sampleOffsetInChunk if _, err = self.r.Seek(int64(sampleOffset), 0); err != nil { return } @@ -303,7 +305,7 @@ func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []b //println("pts/dts", self.ptsEntryIndex, self.dtsEntryIndex) dts = self.dts if self.sample.CompositionOffset != nil && len(self.sample.CompositionOffset.Entries) > 0 { - pts = self.dts+int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset) + pts = self.dts + int64(self.sample.CompositionOffset.Entries[self.cttsEntryIndex].Offset) } else { pts = dts } @@ -312,36 +314,28 @@ func (self *Track) ReadSample() (pts int64, dts int64, isKeyFrame bool, data []b return } -func (self *Track) Duration() float64 { +func (self *Stream) Duration() float64 { total := int64(0) - for _, entry := range(self.sample.TimeToSample.Entries) { - total += int64(entry.Duration*entry.Count) + for _, entry := range self.sample.TimeToSample.Entries { + total += int64(entry.Duration * entry.Count) } - return float64(total)/float64(self.TrackAtom.Media.Header.TimeScale) + return float64(total) / float64(self.trackAtom.Media.Header.TimeScale) } -func (self *Track) CurTime() float64 { - return self.TsToTime(self.dts) -} - -func (self *Track) CurTs() int64 { - return self.dts -} - -func (self *Track) CurSampleIndex() int { +func (self *Stream) CurSampleIndex() int { return self.sampleIndex } -func (self *Track) SeekToTime(time float64) error { +func (self *Stream) SeekToTime(time float64) error { index := self.TimeToSampleIndex(time) return self.setSampleIndex(index) } -func (self *Track) SeekToSampleIndex(index int) error { +func (self *Stream) SeekToSampleIndex(index int) error { return self.setSampleIndex(index) } -func (self *Track) TimeToSampleIndex(time float64) int { +func (self *Stream) TimeToSampleIndex(time float64) int { targetTs := self.TimeToTs(time) targetIndex := 0 @@ -350,11 +344,11 @@ func (self *Track) TimeToSampleIndex(time float64) int { startIndex := 0 endIndex := 0 found := false - for _, entry := range(self.sample.TimeToSample.Entries) { - endTs = startTs+int64(entry.Count*entry.Duration) - endIndex = startIndex+entry.Count + for _, entry := range self.sample.TimeToSample.Entries { + endTs = startTs + int64(entry.Count*entry.Duration) + endIndex = startIndex + entry.Count if targetTs >= startTs && targetTs < endTs { - targetIndex = startIndex+int((targetTs-startTs)/int64(entry.Duration)) + targetIndex = startIndex + int((targetTs-startTs)/int64(entry.Duration)) found = true } startTs = endTs @@ -364,15 +358,15 @@ func (self *Track) TimeToSampleIndex(time float64) int { if targetTs < 0 { targetIndex = 0 } else { - targetIndex = endIndex-1 + targetIndex = endIndex - 1 } } if self.sample.SyncSample != nil { entries := self.sample.SyncSample.Entries - for i := len(entries)-1; i >= 0; i-- { + for i := len(entries) - 1; i >= 0; i-- { if entries[i]-1 < targetIndex { - targetIndex = entries[i]-1 + targetIndex = entries[i] - 1 break } } @@ -380,24 +374,3 @@ func (self *Track) TimeToSampleIndex(time float64) int { return targetIndex } - -func (self *Track) TimeToTs(time float64) int64 { - return int64(time*float64(self.TrackAtom.Media.Header.TimeScale)) -} - -func (self *Track) TsToTime(ts int64) float64 { - return float64(ts)/float64(self.TrackAtom.Media.Header.TimeScale) -} - -func (self *Track) TimeScale() int64 { - return int64(self.TrackAtom.Media.Header.TimeScale) -} - -func (self *Track) GetH264PPSAndSPS() (pps, sps []byte) { - return self.pps, self.sps -} - -func (self *Track) GetMPEG4AudioConfig() isom.MPEG4AudioConfig { - return self.mpeg4AudioConfig -} - diff --git a/example/example.go b/example/example.go index df07b6d..61510df 100644 --- a/example/example.go +++ b/example/example.go @@ -1,11 +1,10 @@ - package main import ( + "encoding/hex" + "fmt" "github.com/nareix/mp4" "os" - "fmt" - "encoding/hex" ) func DemuxExample() { @@ -86,4 +85,3 @@ func main() { //DemuxExample() MuxExample() } - diff --git a/isom/isom.go b/isom/isom.go index 64197c1..f81e3af 100644 --- a/isom/isom.go +++ b/isom/isom.go @@ -82,7 +82,7 @@ var chanConfigTable = []int{ } func IsADTSFrame(frames []byte) bool { - return len(frames) > 7 && frames[0]==0xff&&frames[1]&0xf0==0xf0 + return len(frames) > 7 && frames[0] == 0xff && frames[1]&0xf0 == 0xf0 } func ReadADTSFrame(frame []byte) (config MPEG4AudioConfig, payload []byte, samples int, framelen int, err error) { @@ -90,11 +90,11 @@ func ReadADTSFrame(frame []byte) (config MPEG4AudioConfig, payload []byte, sampl err = fmt.Errorf("not adts frame") return } - config.ObjectType = uint(frame[2]>>6)+1 - config.SampleRateIndex = uint(frame[2]>>2&0xf) - config.ChannelConfig = uint(frame[2]<<2&0x4|frame[3]>>6&0x3) - framelen = int(frame[3]&0x3)<<11|int(frame[4])<<3|int(frame[5]>>5) - samples = (int(frame[6]&0x3)+1)*1024 + config.ObjectType = uint(frame[2]>>6) + 1 + config.SampleRateIndex = uint(frame[2] >> 2 & 0xf) + config.ChannelConfig = uint(frame[2]<<2&0x4 | frame[3]>>6&0x3) + framelen = int(frame[3]&0x3)<<11 | int(frame[4])<<3 | int(frame[5]>>5) + samples = (int(frame[6]&0x3) + 1) * 1024 hdrlen := 7 if frame[1]&0x1 == 0 { hdrlen = 9 @@ -110,16 +110,16 @@ func ReadADTSFrame(frame []byte) (config MPEG4AudioConfig, payload []byte, sampl func MakeADTSHeader(config MPEG4AudioConfig, samples int, payloadLength int) (header []byte) { payloadLength += 7 //AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ) - header = []byte{0xff,0xf1,0x50,0x80,0x043,0xff,0xcd} + header = []byte{0xff, 0xf1, 0x50, 0x80, 0x043, 0xff, 0xcd} //config.ObjectType = uint(frames[2]>>6)+1 //config.SampleRateIndex = uint(frames[2]>>2&0xf) //config.ChannelConfig = uint(frames[2]<<2&0x4|frames[3]>>6&0x3) - header[2] = (byte(config.ObjectType-1)&0x3)<<6|(byte(config.SampleRateIndex)&0xf)<<2|byte(config.ChannelConfig>>2)&0x1 - header[3] = header[3]&0x3f|byte(config.ChannelConfig&0x3)<<6 - header[3] = header[3]&0xfc|byte(payloadLength>>11)&0x3 - header[4] = byte(payloadLength>>3) - header[5] = header[5]&0x1f|(byte(payloadLength)&0x7)<<5 - header[6] = header[6]&0xfc|byte(samples/1024-1) + header[2] = (byte(config.ObjectType-1)&0x3)<<6 | (byte(config.SampleRateIndex)&0xf)<<2 | byte(config.ChannelConfig>>2)&0x1 + header[3] = header[3]&0x3f | byte(config.ChannelConfig&0x3)<<6 + header[3] = header[3]&0xfc | byte(payloadLength>>11)&0x3 + header[4] = byte(payloadLength >> 3) + header[5] = header[5]&0x1f | (byte(payloadLength)&0x7)<<5 + header[6] = header[6]&0xfc | byte(samples/1024-1) return } diff --git a/isom/isom_test.go b/isom/isom_test.go index 29903b1..7af8b16 100644 --- a/isom/isom_test.go +++ b/isom/isom_test.go @@ -46,7 +46,7 @@ func TestReadElemStreamDesc(t *testing.T) { t.Logf("%v n=%d", aconfig.Complete(), n) data = MakeADTSHeader(aconfig, 1024*3, 33) - data = append(data, []byte{1,2,3,4,5}...) + data = append(data, []byte{1, 2, 3, 4, 5}...) t.Logf("%x", data) aconfig, _, n, framelen, err = ReadADTSFrame(data) t.Logf("%v n=%d framelen=%d err=%v", aconfig.Complete(), n, framelen, err) diff --git a/muxer.go b/muxer.go index d4c40b0..bb144f3 100644 --- a/muxer.go +++ b/muxer.go @@ -1,57 +1,54 @@ - package mp4 import ( + "bytes" + "fmt" + "github.com/nareix/av" "github.com/nareix/mp4/atom" "github.com/nareix/mp4/isom" "io" - "bytes" - "fmt" ) type Muxer struct { - W io.WriteSeeker - Tracks []*Track - TrackH264 *Track - TrackAAC *Track - + W io.WriteSeeker + streams []*Stream mdatWriter *atom.Writer } -func (self *Muxer) newTrack() *Track { - track := &Track{} +func (self *Muxer) NewStream() av.Stream { + stream := &Stream{} - track.sample = &atom.SampleTable{ - SampleDesc: &atom.SampleDesc{}, + stream.sample = &atom.SampleTable{ + SampleDesc: &atom.SampleDesc{}, TimeToSample: &atom.TimeToSample{}, SampleToChunk: &atom.SampleToChunk{ Entries: []atom.SampleToChunkEntry{ { - FirstChunk: 1, - SampleDescId: 1, + FirstChunk: 1, + SampleDescId: 1, SamplesPerChunk: 1, }, }, }, - SampleSize: &atom.SampleSize{}, + SampleSize: &atom.SampleSize{}, ChunkOffset: &atom.ChunkOffset{}, } - track.TrackAtom = &atom.Track{ + stream.trackAtom = &atom.Track{ Header: &atom.TrackHeader{ - TrackId: len(self.Tracks)+1, - Flags: 0x0003, // Track enabled | Track in movie - Duration: 0, // fill later - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + TrackId: len(self.streams) + 1, + Flags: 0x0003, // Track enabled | Track in movie + Duration: 0, // fill later + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, }, Media: &atom.Media{ Header: &atom.MediaHeader{ TimeScale: 0, // fill later - Duration: 0, // fill later - Language: 21956, + Duration: 0, // fill later + Language: 21956, }, Info: &atom.MediaInfo{ - Sample: track.sample, + Sample: stream.sample, Data: &atom.DataInfo{ Refer: &atom.DataRefer{ Url: &atom.DataReferUrl{ @@ -63,57 +60,86 @@ func (self *Muxer) newTrack() *Track { }, } - track.writeMdat = self.writeMdat - self.Tracks = append(self.Tracks, track) + stream.writeMdat = self.writeMdat + self.streams = append(self.streams, stream) - return track + return stream } -func (self *Muxer) AddAACTrack() (track *Track) { - track = self.newTrack() - self.TrackAAC = track - track.Type = AAC - track.sample.SampleDesc.Mp4aDesc = &atom.Mp4aDesc{ - DataRefIdx: 1, - NumberOfChannels: 0, // fill later - SampleSize: 0, // fill later - SampleRate: 0, // fill later - Conf: &atom.ElemStreamDesc{}, +func (self *Stream) fillTrackAtom() (err error) { + if self.sampleIndex > 0 { + self.sttsEntry.Count++ } - track.TrackAtom.Header.Volume = atom.IntToFixed(1) - track.TrackAtom.Header.AlternateGroup = 1 - track.TrackAtom.Media.Handler = &atom.HandlerRefer{ - SubType: "soun", - Name: "Sound Handler", - } - track.TrackAtom.Media.Info.Sound = &atom.SoundMediaInfo{} - return -} -func (self *Muxer) AddH264Track() (track *Track) { - track = self.newTrack() - self.TrackH264 = track - track.Type = H264 - track.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ - DataRefIdx: 1, - HorizontalResolution: 72, - VorizontalResolution: 72, - Width: 0, // fill later - Height: 0, // fill later - FrameCount: 1, - Depth: 24, - ColorTableId: -1, - Conf: &atom.Avc1Conf{}, - } - track.sample.SyncSample = &atom.SyncSample{} - track.TrackAtom.Media.Handler = &atom.HandlerRefer{ - SubType: "vide", - Name: "Video Media Handler", - } - track.sample.CompositionOffset = &atom.CompositionOffset{} - track.TrackAtom.Media.Info.Video = &atom.VideoMediaInfo{ - Flags: 0x000001, + self.trackAtom.Media.Header.TimeScale = int(self.TimeScale()) + self.trackAtom.Media.Header.Duration = int(self.lastDts) + + if self.Type() == av.H264 { + self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( + self.sps, + self.pps, + ) + if err != nil { + return + } + var info *atom.H264SPSInfo + if info, err = atom.ParseH264SPS(self.sps[1:]); err != nil { + return + } + self.sample.SampleDesc.Avc1Desc = &atom.Avc1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: int(info.Width), + Height: int(info.Height), + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + Conf: &atom.Avc1Conf{}, + } + self.sample.SyncSample = &atom.SyncSample{} + self.trackAtom.Media.Handler = &atom.HandlerRefer{ + SubType: "vide", + Name: "Video Media Handler", + } + self.sample.CompositionOffset = &atom.CompositionOffset{} + self.trackAtom.Media.Info.Video = &atom.VideoMediaInfo{ + Flags: 0x000001, + } + self.trackAtom.Header.TrackWidth = atom.IntToFixed(int(info.Width)) + self.trackAtom.Header.TrackHeight = atom.IntToFixed(int(info.Height)) + + } else if self.Type() == av.AAC { + if !self.mpeg4AudioConfig.IsValid() { + err = fmt.Errorf("invalie MPEG4AudioConfig") + return + } + buf := &bytes.Buffer{} + config := self.mpeg4AudioConfig.Complete() + if err = isom.WriteElemStreamDescAAC(buf, config, uint(self.trackAtom.Header.TrackId)); err != nil { + return + } + self.sample.SampleDesc.Mp4aDesc = &atom.Mp4aDesc{ + DataRefIdx: 1, + NumberOfChannels: config.ChannelCount, + SampleSize: config.ChannelCount * 8, + SampleRate: atom.IntToFixed(config.SampleRate), + Conf: &atom.ElemStreamDesc{ + Data: buf.Bytes(), + }, + } + self.trackAtom.Header.Volume = atom.IntToFixed(1) + self.trackAtom.Header.AlternateGroup = 1 + self.trackAtom.Media.Handler = &atom.HandlerRefer{ + SubType: "soun", + Name: "Sound Handler", + } + self.trackAtom.Media.Info.Sound = &atom.SoundMediaInfo{} + + } else { + err = fmt.Errorf("please specify stream type") } + return } @@ -132,22 +158,12 @@ func (self *Muxer) WriteHeader() (err error) { return } -func (self *Track) SetH264PPSAndSPS(pps, sps []byte) { - self.pps, self.sps = pps, sps -} +func (self *Muxer) WriteSample(pkt av.Packet) (err error) { + pts, dts, isKeyFrame, frame := pkt.Pts, pkt.Dts, pkt.IsKeyFrame, pkt.Data + stream := self.streams[pkt.StreamIdx] -func (self *Track) SetMPEG4AudioConfig(config isom.MPEG4AudioConfig) { - self.mpeg4AudioConfig = config -} - -func (self *Track) SetTimeScale(timeScale int64) { - self.TrackAtom.Media.Header.TimeScale = int(timeScale) - return -} - -func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, frame []byte) (err error) { - if self.Type == AAC && isom.IsADTSFrame(frame) { - config := self.mpeg4AudioConfig.Complete() + if stream.Type() == av.AAC && isom.IsADTSFrame(frame) { + config := stream.mpeg4AudioConfig.Complete() if config.SampleRate == 0 { err = fmt.Errorf("invalid sample rate") return @@ -159,19 +175,20 @@ func (self *Track) WriteSample(pts int64, dts int64, isKeyFrame bool, frame []by if _, payload, samples, framelen, err = isom.ReadADTSFrame(frame); err != nil { return } - delta := int64(samples)*self.TimeScale()/int64(config.SampleRate) + delta := int64(samples) * stream.TimeScale() / int64(config.SampleRate) pts += delta dts += delta frame = frame[framelen:] - if self.writeSample(pts, dts, isKeyFrame, payload); err != nil { + if stream.writeSample(pts, dts, isKeyFrame, payload); err != nil { return } } } - return self.writeSample(pts, dts, isKeyFrame, frame) + + return stream.writeSample(pts, dts, isKeyFrame, frame) } -func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { +func (self *Stream) writeSample(pts int64, dts int64, isKeyFrame bool, data []byte) (err error) { var filePos int64 sampleSize := len(data) if filePos, err = self.writeMdat(data); err != nil { @@ -187,7 +204,7 @@ func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byt err = fmt.Errorf("dts must be incremental") return } - duration := int(dts-self.lastDts) + duration := int(dts - self.lastDts) if self.sttsEntry == nil || duration != self.sttsEntry.Duration { self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, atom.TimeToSampleEntry{Duration: duration}) self.sttsEntry = &self.sample.TimeToSample.Entries[len(self.sample.TimeToSample.Entries)-1] @@ -200,7 +217,7 @@ func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byt err = fmt.Errorf("pts must greater than dts") return } - offset := int(pts-dts) + offset := int(pts - dts) if self.cttsEntry == nil || offset != self.cttsEntry.Offset { table := self.sample.CompositionOffset table.Entries = append(table.Entries, atom.CompositionOffsetEntry{Offset: offset}) @@ -217,71 +234,30 @@ func (self *Track) writeSample(pts int64, dts int64, isKeyFrame bool, data []byt return } -func (self *Track) fillTrackAtom() (err error) { - if self.sampleIndex > 0 { - self.sttsEntry.Count++ - } - - if self.Type == H264 { - self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( - self.sps, - self.pps, - ) - if err != nil { - return - } - var info *atom.H264SPSInfo - if info, err = atom.ParseH264SPS(self.sps[1:]); err != nil { - return - } - self.sample.SampleDesc.Avc1Desc.Width = int(info.Width) - self.sample.SampleDesc.Avc1Desc.Height = int(info.Height) - self.TrackAtom.Header.TrackWidth = atom.IntToFixed(int(info.Width)) - self.TrackAtom.Header.TrackHeight = atom.IntToFixed(int(info.Height)) - self.TrackAtom.Media.Header.Duration = int(self.lastDts) - } else if self.Type == AAC { - if !self.mpeg4AudioConfig.IsValid() { - err = fmt.Errorf("invalie MPEG4AudioConfig") - return - } - buf := &bytes.Buffer{} - config := self.mpeg4AudioConfig.Complete() - if err = isom.WriteElemStreamDescAAC(buf, config, uint(self.TrackAtom.Header.TrackId)); err != nil { - return - } - self.sample.SampleDesc.Mp4aDesc.Conf.Data = buf.Bytes() - self.sample.SampleDesc.Mp4aDesc.NumberOfChannels = config.ChannelCount - self.sample.SampleDesc.Mp4aDesc.SampleSize = config.ChannelCount*8 - self.sample.SampleDesc.Mp4aDesc.SampleRate = atom.IntToFixed(config.SampleRate) - self.TrackAtom.Media.Header.Duration = int(self.lastDts) - } - return -} - func (self *Muxer) WriteTrailer() (err error) { moov := &atom.Movie{} moov.Header = &atom.MovieHeader{ - PreferredRate: atom.IntToFixed(1), + PreferredRate: atom.IntToFixed(1), PreferredVolume: atom.IntToFixed(1), - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - NextTrackId: 2, + Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, + NextTrackId: 2, } maxDur := float64(0) timeScale := 10000 - for _, track := range(self.Tracks) { - if err = track.fillTrackAtom(); err != nil { + for _, stream := range self.streams { + if err = stream.fillTrackAtom(); err != nil { return } - dur := track.Duration() - track.TrackAtom.Header.Duration = int(float64(timeScale)*dur) + dur := stream.Duration() + stream.trackAtom.Header.Duration = int(float64(timeScale) * dur) if dur > maxDur { maxDur = dur } - moov.Tracks = append(moov.Tracks, track.TrackAtom) + moov.Tracks = append(moov.Tracks, stream.trackAtom) } moov.Header.TimeScale = timeScale - moov.Header.Duration = int(float64(timeScale)*maxDur) + moov.Header.Duration = int(float64(timeScale) * maxDur) if err = self.mdatWriter.Close(); err != nil { return @@ -292,226 +268,3 @@ func (self *Muxer) WriteTrailer() (err error) { return } - -/* -type SimpleH264Writer struct { - W io.WriteSeeker - - TimeScale int - sps []byte - pps []byte - - Width int - Height int - - duration int - - sample *atom.SampleTable - sampleToChunk *atom.SampleToChunkEntry - sampleIdx int - timeToSample *atom.TimeToSampleEntry - - mdatWriter *atom.Writer -} - -func (self *SimpleH264Writer) prepare() (err error) { - if self.mdatWriter, err = atom.WriteAtomHeader(self.W, "mdat"); err != nil { - return - } - - if len(self.sps) == 0 { - err = fmt.Errorf("invalid sps") - return - } - - if len(self.pps) == 0 { - err = fmt.Errorf("invalid pps") - return - } - - if self.Width == 0 || self.Height == 0 { - var info *atom.H264spsInfo - if info, err = atom.ParseH264sps(self.sps[1:]); err != nil { - return - } - self.Width = int(info.Width) - self.Height = int(info.Height) - } - - self.sampleIdx = 1 - - self.sample = &atom.SampleTable{ - SampleDesc: &atom.SampleDesc{ - Avc1Desc: &atom.Avc1Desc{ - DataRefIdx: 1, - HorizontalResolution: 72, - VorizontalResolution: 72, - Width: self.Width, - Height: self.Height, - FrameCount: 1, - Depth: 24, - ColorTableId: -1, - Conf: &atom.Avc1Conf{}, - }, - }, - TimeToSample: &atom.TimeToSample{}, - SampleToChunk: &atom.SampleToChunk{ - Entries: []atom.SampleToChunkEntry{ - { - FirstChunk: 1, - SampleDescId: 1, - }, - }, - }, - SampleSize: &atom.SampleSize{}, - ChunkOffset: &atom.ChunkOffset{ - Entries: []int{8}, - }, - SyncSample: &atom.SyncSample{}, - } - self.sampleToChunk = &self.sample.SampleToChunk.Entries[0] - - return -} - -func (self *SimpleH264Writer) WriteSample(sync bool, duration int, data []byte) (err error) { - return self.writeSample(false, sync, duration, data) -} - -func (self *SimpleH264Writer) WriteNALU(sync bool, duration int, data []byte) (err error) { - return self.writeSample(true, sync, duration, data) -} - -func splitNALUByStartCode(data []byte) (out [][]byte) { - last := 0 - for i := 0; i < len(data)-3; { - if data[i] == 0 && data[i+1] == 0 && data[i+2] == 1 { - out = append(out, data[last:i]) - i += 3 - last = i - } else { - i++ - } - } - out = append(out, data[last:]) - return -} - -func (self *SimpleH264Writer) writeSample(isNALU, sync bool, duration int, data []byte) (err error) { - if self.mdatWriter == nil { - if err = self.prepare(); err != nil { - return - } - } - - var sampleSize int - - if isNALU { - if sampleSize, err = atom.WriteSampleByNALU(self.mdatWriter, data); err != nil { - return - } - } else { - sampleSize = len(data) - if _, err = self.mdatWriter.Write(data); err != nil { - return - } - } - - if sync { - self.sample.SyncSample.Entries = append(self.sample.SyncSample.Entries, self.sampleIdx) - } - - if self.timeToSample != nil && duration != self.timeToSample.Duration { - self.sample.TimeToSample.Entries = append(self.sample.TimeToSample.Entries, *self.timeToSample) - self.timeToSample = nil - } - if self.timeToSample == nil { - self.timeToSample = &atom.TimeToSampleEntry{ - Duration: duration, - } - } - - self.duration += duration - self.sampleIdx++ - self.timeToSample.Count++ - self.sampleToChunk.SamplesPerChunk++ - self.sample.SampleSize.Entries = append(self.sample.SampleSize.Entries, sampleSize) - - return -} - -func (self *SimpleH264Writer) Finish() (err error) { - self.sample.SampleDesc.Avc1Desc.Conf.Record, err = atom.CreateAVCDecoderConfRecord( - self.sps, - self.pps, - ) - if err != nil { - return - } - - if self.timeToSample != nil { - self.sample.TimeToSample.Entries = append( - self.sample.TimeToSample.Entries, - *self.timeToSample, - ) - } - - if err = self.mdatWriter.Close(); err != nil { - return - } - - moov := &atom.Movie{} - moov.Header = &atom.MovieHeader{ - TimeScale: self.TimeScale, - Duration: self.duration, - PreferredRate: atom.IntToFixed(1), - PreferredVolume: atom.IntToFixed(1), - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - NextTrackId: 2, - } - - track := &atom.Track{ - Header: &atom.TrackHeader{ - TrackId: 1, - Flags: 0x0003, // Track enabled | Track in movie - Duration: self.duration, - Volume: atom.IntToFixed(1), - Matrix: [9]int{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, - TrackWidth: atom.IntToFixed(self.Width), - TrackHeight: atom.IntToFixed(self.Height), - }, - - Media: &atom.Media{ - Header: &atom.MediaHeader{ - TimeScale: self.TimeScale, - Duration: self.duration, - }, - Info: &atom.MediaInfo{ - Video: &atom.VideoMediaInfo{ - Flags: 0x000001, - }, - Sample: self.sample, - Data: &atom.DataInfo{ - Refer: &atom.DataRefer{ - Url: &atom.DataReferUrl{ - Flags: 0x000001, // Self reference - }, - }, - }, - }, - Handler: &atom.HandlerRefer{ - SubType: "vide", - Name: "Video Media Handler", - }, - }, - } - moov.Tracks = append(moov.Tracks, track) - - if err = atom.WriteMovie(self.W, moov); err != nil { - return - } - - return -} -*/ - diff --git a/track.go b/track.go deleted file mode 100644 index 0d13ef2..0000000 --- a/track.go +++ /dev/null @@ -1,47 +0,0 @@ - -package mp4 - -import ( - "github.com/nareix/mp4/atom" - "github.com/nareix/mp4/isom" - "io" -) - -const ( - H264 = 1 - AAC = 2 -) - -type Track struct { - Type int - TrackAtom *atom.Track - r io.ReadSeeker - - sps []byte - pps []byte - - mpeg4AudioConfig isom.MPEG4AudioConfig - - sample *atom.SampleTable - sampleIndex int - - sampleOffsetInChunk int64 - syncSampleIndex int - - dts int64 - sttsEntryIndex int - sampleIndexInSttsEntry int - - cttsEntryIndex int - sampleIndexInCttsEntry int - - chunkGroupIndex int - chunkIndex int - sampleIndexInChunk int - - sttsEntry *atom.TimeToSampleEntry - cttsEntry *atom.CompositionOffsetEntry - writeMdat func ([]byte) (int64,error) - lastDts int64 -} -