package mp4f import ( "bufio" "fmt" "os" "time" "gitlab.com/ics_cinnamon/joy4/av" "gitlab.com/ics_cinnamon/joy4/codec/aacparser" "gitlab.com/ics_cinnamon/joy4/codec/h264parser" "gitlab.com/ics_cinnamon/joy4/format/mp4/mp4io" "gitlab.com/ics_cinnamon/joy4/format/mp4f/mp4fio" "gitlab.com/ics_cinnamon/joy4/utils/bits/pio" ) type Muxer struct { maxFrames int bufw *bufio.Writer wpos int64 fragmentIndex int streams []*Stream path string } func NewMuxer(w *os.File) *Muxer { return &Muxer{} } func (self *Muxer) SetPath(path string) { self.path = path } func (self *Muxer) SetMaxFrames(count int) { self.maxFrames = count } func (self *Muxer) newStream(codec av.CodecData) (err error) { switch codec.Type() { case av.H264, av.AAC: default: err = fmt.Errorf("fmp4: codec type=%v is not supported", codec.Type()) return } stream := &Stream{CodecData: codec} stream.sample = &mp4io.SampleTable{ SampleDesc: &mp4io.SampleDesc{}, TimeToSample: &mp4io.TimeToSample{}, SampleToChunk: &mp4io.SampleToChunk{}, SampleSize: &mp4io.SampleSize{}, ChunkOffset: &mp4io.ChunkOffset{}, } stream.trackAtom = &mp4io.Track{ Header: &mp4io.TrackHeader{ TrackId: int32(len(self.streams) + 1), Flags: 0x0007, Duration: 0, Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, }, Media: &mp4io.Media{ Header: &mp4io.MediaHeader{ TimeScale: 1000, Duration: 0, Language: 21956, }, Info: &mp4io.MediaInfo{ Sample: stream.sample, Data: &mp4io.DataInfo{ Refer: &mp4io.DataRefer{ Url: &mp4io.DataReferUrl{ Flags: 0x000001, }, }, }, }, }, } switch codec.Type() { case av.H264: stream.sample.SyncSample = &mp4io.SyncSample{} stream.timeScale = 90000 case av.AAC: stream.timeScale = 8000 } stream.muxer = self self.streams = append(self.streams, stream) return } func (self *Stream) buildEsds(conf []byte) *FDummy { esds := &mp4fio.ElemStreamDesc{DecConfig: conf} b := make([]byte, esds.Len()) esds.Marshal(b) esdsDummy := FDummy{ Data: b, Tag_: mp4io.Tag(uint32(mp4io.ESDS)), } return &esdsDummy } func (self *Stream) buildHdlr() *FDummy { hdlr := FDummy{ Data: []byte{ 0x00, 0x00, 0x00, 0x35, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x6F, 0x6F, 0x64, 0x67, 0x61, 0x6D, 0x65, 0x20, 0x47, 0x4F, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x00, 0x00, 0x00}, Tag_: mp4io.Tag(uint32(mp4io.HDLR)), } return &hdlr } func (self *Stream) buildAudioHdlr() *FDummy { hdlr := FDummy{ Data: []byte{ 0x00, 0x00, 0x00, 0x35, 0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x6F, 0x75, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x65, 0x6E, 0x74, 0x6F, 0x34, 0x20, 0x53, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x72, 0x00}, Tag_: mp4io.Tag(uint32(mp4io.HDLR)), } return &hdlr } func (self *Stream) buildEdts() *FDummy { edts := FDummy{ Data: []byte{ 0x00, 0x00, 0x00, 0x30, 0x65, 0x64, 0x74, 0x73, 0x00, 0x00, 0x00, 0x28, 0x65, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x9B, 0x24, 0x00, 0x00, 0x02, 0x10, 0x00, 0x01, 0x00, 0x00, }, Tag_: mp4io.Tag(0x65647473), } return &edts } func (self *Stream) fillTrackAtom() (err error) { self.trackAtom.Media.Header.TimeScale = int32(self.timeScale) self.trackAtom.Media.Header.Duration = int32(self.duration) if self.Type() == av.H264 { codec := self.CodecData.(h264parser.CodecData) width, height := codec.Width(), codec.Height() self.sample.SampleDesc.AVC1Desc = &mp4io.AVC1Desc{ DataRefIdx: 1, HorizontalResolution: 72, VorizontalResolution: 72, Width: int16(width), Height: int16(height), FrameCount: 1, Depth: 24, ColorTableId: -1, Conf: &mp4io.AVC1Conf{Data: codec.AVCDecoderConfRecordBytes()}, } self.trackAtom.Header.TrackWidth = float64(width) self.trackAtom.Header.TrackHeight = float64(height) self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ SubType: [4]byte{'v', 'i', 'd', 'e'}, Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'G', 'G', 0, 0, 0}, } self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{ Flags: 0x000001, } self.codecString = fmt.Sprintf("avc1.%02X%02X%02X", codec.RecordInfo.AVCProfileIndication, codec.RecordInfo.ProfileCompatibility, codec.RecordInfo.AVCLevelIndication) } else if self.Type() == av.AAC { codec := self.CodecData.(aacparser.CodecData) self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{ DataRefIdx: 1, NumberOfChannels: int16(codec.ChannelLayout().Count()), SampleSize: 16, SampleRate: float64(codec.SampleRate()), Unknowns: []mp4io.Atom{self.buildEsds(codec.MPEG4AudioConfigBytes())}, } self.trackAtom.Header.Volume = 1 self.trackAtom.Header.AlternateGroup = 1 self.trackAtom.Header.Duration = 0 self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ SubType: [4]byte{'s', 'o', 'u', 'n'}, Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'G', 'G', 0, 0, 0}, } self.trackAtom.Media.Info.Sound = &mp4io.SoundMediaInfo{} self.codecString = "mp4a.40.2" } else { err = fmt.Errorf("fmp4: codec type=%d invalid", self.Type()) } return } func (self *Muxer) WriteTrailer() (err error) { return } func (element *Muxer) WriteHeader(streams []av.CodecData) (err error) { element.streams = []*Stream{} for _, stream := range streams { if err = element.newStream(stream); err != nil { return } } return } func (element *Muxer) GetInit(streams []av.CodecData) (string, []byte) { moov := &mp4io.Movie{ Header: &mp4io.MovieHeader{ PreferredRate: 1, PreferredVolume: 1, Matrix: [9]int32{0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000}, NextTrackId: 3, Duration: 0, TimeScale: 1000, CreateTime: time0(), ModifyTime: time0(), PreviewTime: time0(), PreviewDuration: time0(), PosterTime: time0(), SelectionTime: time0(), SelectionDuration: time0(), CurrentTime: time0(), }, Unknowns: []mp4io.Atom{element.buildMvex()}, } var meta string for _, stream := range element.streams { if err := stream.fillTrackAtom(); err != nil { return meta, []byte{} } moov.Tracks = append(moov.Tracks, stream.trackAtom) meta += stream.codecString + "," } meta = meta[:len(meta)-1] ftypeData := []byte{0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x36, 0x00, 0x00, 0x00, 0x01, 0x69, 0x73, 0x6f, 0x36, 0x64, 0x61, 0x73, 0x68} file := make([]byte, moov.Len()+len(ftypeData)) copy(file, ftypeData) moov.Marshal(file[len(ftypeData):]) return meta, file } func (element *Muxer) WritePacket(pkt av.Packet, GOP bool) (bool, []byte, error) { stream := element.streams[pkt.Idx] if GOP { ts := time.Duration(0) if stream.lastpkt != nil { ts = pkt.Time - stream.lastpkt.Time } got, buf, err := stream.writePacketV3(pkt, ts, 5) stream.lastpkt = &pkt if err != nil { return false, []byte{}, err } return got, buf, err } ts := time.Duration(0) if stream.lastpkt != nil { ts = pkt.Time - stream.lastpkt.Time } got, buf, err := stream.writePacketV2(pkt, ts, 5) stream.lastpkt = &pkt if err != nil { return false, []byte{}, err } return got, buf, err } func (element *Stream) writePacketV3(pkt av.Packet, rawdur time.Duration, maxFrames int) (bool, []byte, error) { trackID := pkt.Idx + 1 var out []byte var got bool if element.sampleIndex > maxFrames && pkt.IsKeyFrame { element.moof.Tracks[0].Run.DataOffset = uint32(element.moof.Len() + 8) out = make([]byte, element.moof.Len()+len(element.buffer)) element.moof.Marshal(out) pio.PutU32BE(element.buffer, uint32(len(element.buffer))) copy(out[element.moof.Len():], element.buffer) element.sampleIndex = 0 element.muxer.fragmentIndex++ got = true } if element.sampleIndex == 0 { element.moof.Header = &mp4fio.MovieFragHeader{Seqnum: uint32(element.muxer.fragmentIndex + 1)} element.moof.Tracks = []*mp4fio.TrackFrag{ &mp4fio.TrackFrag{ Header: &mp4fio.TrackFragHeader{ Data: []byte{0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, uint8(trackID), 0x01, 0x01, 0x00, 0x00}, }, DecodeTime: &mp4fio.TrackFragDecodeTime{ Version: 1, Flags: 0, Time: uint64(element.dts), }, Run: &mp4fio.TrackFragRun{ Flags: 0x000b05, FirstSampleFlags: 0x02000000, DataOffset: 0, Entries: []mp4io.TrackFragRunEntry{}, }, }, } element.buffer = []byte{0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x61, 0x74} } runEnrty := mp4io.TrackFragRunEntry{ Duration: uint32(element.timeToTs(rawdur)), Size: uint32(len(pkt.Data)), Cts: uint32(element.timeToTs(pkt.CompositionTime)), } element.moof.Tracks[0].Run.Entries = append(element.moof.Tracks[0].Run.Entries, runEnrty) element.buffer = append(element.buffer, pkt.Data...) element.sampleIndex++ element.dts += element.timeToTs(rawdur) return got, out, nil } func (element *Stream) writePacketV2(pkt av.Packet, rawdur time.Duration, maxFrames int) (bool, []byte, error) { trackID := pkt.Idx + 1 if element.sampleIndex == 0 { element.moof.Header = &mp4fio.MovieFragHeader{Seqnum: uint32(element.muxer.fragmentIndex + 1)} element.moof.Tracks = []*mp4fio.TrackFrag{ &mp4fio.TrackFrag{ Header: &mp4fio.TrackFragHeader{ Data: []byte{0x00, 0x02, 0x00, 0x20, 0x00, 0x00, 0x00, uint8(trackID), 0x01, 0x01, 0x00, 0x00}, }, DecodeTime: &mp4fio.TrackFragDecodeTime{ Version: 1, Flags: 0, Time: uint64(element.dts), }, Run: &mp4fio.TrackFragRun{ Flags: 0x000b05, FirstSampleFlags: 0x02000000, DataOffset: 0, Entries: []mp4io.TrackFragRunEntry{}, }, }, } element.buffer = []byte{0x00, 0x00, 0x00, 0x00, 0x6d, 0x64, 0x61, 0x74} } runEnrty := mp4io.TrackFragRunEntry{ Duration: uint32(element.timeToTs(rawdur)), Size: uint32(len(pkt.Data)), Cts: uint32(element.timeToTs(pkt.CompositionTime)), } element.moof.Tracks[0].Run.Entries = append(element.moof.Tracks[0].Run.Entries, runEnrty) element.buffer = append(element.buffer, pkt.Data...) element.sampleIndex++ element.dts += element.timeToTs(rawdur) if element.sampleIndex > maxFrames { // Количество фреймов в пакете element.moof.Tracks[0].Run.DataOffset = uint32(element.moof.Len() + 8) file := make([]byte, element.moof.Len()+len(element.buffer)) element.moof.Marshal(file) pio.PutU32BE(element.buffer, uint32(len(element.buffer))) copy(file[element.moof.Len():], element.buffer) element.sampleIndex = 0 element.muxer.fragmentIndex++ return true, file, nil } return false, []byte{}, nil } func (self *Muxer) buildMvex() *FDummy { mvex := &FDummy{ Data: []byte{ 0x00, 0x00, 0x00, 0x38, 0x6D, 0x76, 0x65, 0x78, 0x00, 0x00, 0x00, 0x10, 0x6D, 0x65, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, Tag_: mp4io.Tag(0x6D766578), } for i := 1; i <= len(self.streams); i++ { trex := self.buildTrex(i) mvex.Data = append(mvex.Data, trex...) } pio.PutU32BE(mvex.Data, uint32(len(mvex.Data))) return mvex } func (self *Muxer) buildTrex(trackId int) []byte { return []byte{ 0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, uint8(trackId), 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} } func time0() time.Time { return time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC) }