diff --git a/format/mp4/mp4io/atoms.go b/format/mp4/mp4io/atoms.go index de8a818..dd3c3fb 100644 --- a/format/mp4/mp4io/atoms.go +++ b/format/mp4/mp4io/atoms.go @@ -1,7 +1,10 @@ package mp4io -import "github.com/deepch/vdk/utils/bits/pio" -import "time" +import ( + "time" + + "github.com/deepch/vdk/utils/bits/pio" +) const MOOF = Tag(0x6d6f6f66) @@ -21,6 +24,17 @@ func (self AVC1Desc) Tag() Tag { return AVC1 } +//0x31766568 +const HEV1 = Tag(0x68766331) + +func (self HV1Desc) Tag() Tag { + return HEV1 +} + +//const HVC1 = Tag(0x68766331) +//func (self HVC1Desc) Tag() Tag { +// return HVC1 +//} const URL = Tag(0x75726c20) func (self DataReferUrl) Tag() Tag { @@ -153,6 +167,12 @@ func (self AVC1Conf) Tag() Tag { return AVCC } +const HVCC = Tag(0x68766343) + +func (self HV1Conf) Tag() Tag { + return HVCC +} + const TFDT = Tag(0x74666474) func (self TrackFragDecodeTime) Tag() Tag { @@ -1682,6 +1702,7 @@ func (self SampleTable) Children() (r []Atom) { type SampleDesc struct { Version uint8 AVC1Desc *AVC1Desc + HV1Desc *HV1Desc MP4ADesc *MP4ADesc Unknowns []Atom AtomPos @@ -1701,6 +1722,9 @@ func (self SampleDesc) marshal(b []byte) (n int) { if self.AVC1Desc != nil { _childrenNR++ } + if self.HV1Desc != nil { + _childrenNR++ + } if self.MP4ADesc != nil { _childrenNR++ } @@ -1710,6 +1734,9 @@ func (self SampleDesc) marshal(b []byte) (n int) { if self.AVC1Desc != nil { n += self.AVC1Desc.Marshal(b[n:]) } + if self.HV1Desc != nil { + n += self.HV1Desc.Marshal(b[n:]) + } if self.MP4ADesc != nil { n += self.MP4ADesc.Marshal(b[n:]) } @@ -1726,6 +1753,9 @@ func (self SampleDesc) Len() (n int) { if self.AVC1Desc != nil { n += self.AVC1Desc.Len() } + if self.HV1Desc != nil { + n += self.HV1Desc.Len() + } if self.MP4ADesc != nil { n += self.MP4ADesc.Len() } @@ -1762,6 +1792,15 @@ func (self *SampleDesc) Unmarshal(b []byte, offset int) (n int, err error) { } self.AVC1Desc = atom } + case HEV1: + { + atom := &HV1Desc{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("hec1", n+offset, err) + return + } + self.HV1Desc = atom + } case MP4A: { atom := &MP4ADesc{} @@ -1789,6 +1828,9 @@ func (self SampleDesc) Children() (r []Atom) { if self.AVC1Desc != nil { r = append(r, self.AVC1Desc) } + if self.HV1Desc != nil { + r = append(r, self.HV1Desc) + } if self.MP4ADesc != nil { r = append(r, self.MP4ADesc) } @@ -1955,6 +1997,25 @@ func (self MP4ADesc) Children() (r []Atom) { return } +type HV1Desc struct { + DataRefIdx int16 + Version int16 + Revision int16 + Vendor int32 + TemporalQuality int32 + SpatialQuality int32 + Width int16 + Height int16 + HorizontalResolution float64 + VorizontalResolution float64 + FrameCount int16 + CompressorName [32]byte + Depth int16 + ColorTableId int16 + Conf *HV1Conf + Unknowns []Atom + AtomPos +} type AVC1Desc struct { DataRefIdx int16 Version int16 @@ -1981,6 +2042,12 @@ func (self AVC1Desc) Marshal(b []byte) (n int) { pio.PutU32BE(b[0:], uint32(n)) return } +func (self HV1Desc) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(HEV1)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} func (self AVC1Desc) marshal(b []byte) (n int) { n += 6 pio.PutI16BE(b[n:], self.DataRefIdx) @@ -2020,6 +2087,45 @@ func (self AVC1Desc) marshal(b []byte) (n int) { } return } +func (self HV1Desc) marshal(b []byte) (n int) { + n += 6 + pio.PutI16BE(b[n:], self.DataRefIdx) + n += 2 + pio.PutI16BE(b[n:], self.Version) + n += 2 + pio.PutI16BE(b[n:], self.Revision) + n += 2 + pio.PutI32BE(b[n:], self.Vendor) + n += 4 + pio.PutI32BE(b[n:], self.TemporalQuality) + n += 4 + pio.PutI32BE(b[n:], self.SpatialQuality) + n += 4 + pio.PutI16BE(b[n:], self.Width) + n += 2 + pio.PutI16BE(b[n:], self.Height) + n += 2 + PutFixed32(b[n:], self.HorizontalResolution) + n += 4 + PutFixed32(b[n:], self.VorizontalResolution) + n += 4 + n += 4 + pio.PutI16BE(b[n:], self.FrameCount) + n += 2 + copy(b[n:], self.CompressorName[:]) + n += len(self.CompressorName[:]) + pio.PutI16BE(b[n:], self.Depth) + n += 2 + pio.PutI16BE(b[n:], self.ColorTableId) + n += 2 + if self.Conf != nil { + n += self.Conf.Marshal(b[n:]) + } + for _, atom := range self.Unknowns { + n += atom.Marshal(b[n:]) + } + return +} func (self AVC1Desc) Len() (n int) { n += 8 n += 6 @@ -2046,6 +2152,32 @@ func (self AVC1Desc) Len() (n int) { } return } +func (self HV1Desc) Len() (n int) { + n += 8 + n += 6 + n += 2 + n += 2 + n += 2 + n += 4 + n += 4 + n += 4 + n += 2 + n += 2 + n += 4 + n += 4 + n += 4 + n += 2 + n += len(self.CompressorName[:]) + n += 2 + n += 2 + if self.Conf != nil { + n += self.Conf.Len() + } + for _, atom := range self.Unknowns { + n += atom.Len() + } + return +} func (self *AVC1Desc) Unmarshal(b []byte, offset int) (n int, err error) { (&self.AtomPos).setPos(offset, len(b)) n += 8 @@ -2166,6 +2298,126 @@ func (self *AVC1Desc) Unmarshal(b []byte, offset int) (n int, err error) { } return } +func (self *HV1Desc) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + n += 6 + if len(b) < n+2 { + err = parseErr("DataRefIdx", n+offset, err) + return + } + self.DataRefIdx = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Version", n+offset, err) + return + } + self.Version = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Revision", n+offset, err) + return + } + self.Revision = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("Vendor", n+offset, err) + return + } + self.Vendor = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("TemporalQuality", n+offset, err) + return + } + self.TemporalQuality = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("SpatialQuality", n+offset, err) + return + } + self.SpatialQuality = pio.I32BE(b[n:]) + n += 4 + if len(b) < n+2 { + err = parseErr("Width", n+offset, err) + return + } + self.Width = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("Height", n+offset, err) + return + } + self.Height = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+4 { + err = parseErr("HorizontalResolution", n+offset, err) + return + } + self.HorizontalResolution = GetFixed32(b[n:]) + n += 4 + if len(b) < n+4 { + err = parseErr("VorizontalResolution", n+offset, err) + return + } + self.VorizontalResolution = GetFixed32(b[n:]) + n += 4 + n += 4 + if len(b) < n+2 { + err = parseErr("FrameCount", n+offset, err) + return + } + self.FrameCount = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+len(self.CompressorName) { + err = parseErr("CompressorName", n+offset, err) + return + } + copy(self.CompressorName[:], b[n:]) + n += len(self.CompressorName) + if len(b) < n+2 { + err = parseErr("Depth", n+offset, err) + return + } + self.Depth = pio.I16BE(b[n:]) + n += 2 + if len(b) < n+2 { + err = parseErr("ColorTableId", n+offset, err) + return + } + self.ColorTableId = pio.I16BE(b[n:]) + n += 2 + for n+8 < len(b) { + tag := Tag(pio.U32BE(b[n+4:])) + size := int(pio.U32BE(b[n:])) + if len(b) < n+size { + err = parseErr("TagSizeInvalid", n+offset, err) + return + } + switch tag { + case HVCC: + { + atom := &HV1Conf{} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("hvcC", n+offset, err) + return + } + self.Conf = atom + } + default: + { + atom := &Dummy{Tag_: tag, Data: b[n : n+size]} + if _, err = atom.Unmarshal(b[n:n+size], offset+n); err != nil { + err = parseErr("", n+offset, err) + return + } + self.Unknowns = append(self.Unknowns, atom) + } + } + n += size + } + return +} func (self AVC1Desc) Children() (r []Atom) { if self.Conf != nil { r = append(r, self.Conf) @@ -2173,12 +2425,24 @@ func (self AVC1Desc) Children() (r []Atom) { r = append(r, self.Unknowns...) return } +func (self HV1Desc) Children() (r []Atom) { + if self.Conf != nil { + r = append(r, self.Conf) + } + r = append(r, self.Unknowns...) + return +} type AVC1Conf struct { Data []byte AtomPos } +type HV1Conf struct { + Data []byte + AtomPos +} + func (self AVC1Conf) Marshal(b []byte) (n int) { pio.PutU32BE(b[4:], uint32(AVCC)) n += self.marshal(b[8:]) + 8 @@ -2206,6 +2470,36 @@ func (self AVC1Conf) Children() (r []Atom) { return } +/* +HVEC +*/ +func (self HV1Conf) Marshal(b []byte) (n int) { + pio.PutU32BE(b[4:], uint32(HVCC)) + n += self.marshal(b[8:]) + 8 + pio.PutU32BE(b[0:], uint32(n)) + return +} +func (self HV1Conf) marshal(b []byte) (n int) { + copy(b[n:], self.Data[:]) + n += len(self.Data[:]) + return +} +func (self HV1Conf) Len() (n int) { + n += 8 + n += len(self.Data[:]) + return +} +func (self *HV1Conf) Unmarshal(b []byte, offset int) (n int, err error) { + (&self.AtomPos).setPos(offset, len(b)) + n += 8 + self.Data = b[n:] + n += len(b[n:]) + return +} +func (self HV1Conf) Children() (r []Atom) { + return +} + type TimeToSample struct { Version uint8 Flags uint32 diff --git a/format/mp4/muxer.go b/format/mp4/muxer.go index f7b2735..32b6869 100644 --- a/format/mp4/muxer.go +++ b/format/mp4/muxer.go @@ -29,7 +29,7 @@ func NewMuxer(w io.WriteSeeker) *Muxer { func (self *Muxer) newStream(codec av.CodecData) (err error) { switch codec.Type() { - case av.H264, av.AAC: + case av.H264, av.H265, av.AAC: default: err = fmt.Errorf("mp4: codec type=%v is not supported", codec.Type()) @@ -82,6 +82,8 @@ func (self *Muxer) newStream(codec av.CodecData) (err error) { switch codec.Type() { case av.H264: stream.sample.SyncSample = &mp4io.SyncSample{} + case av.H265: + stream.sample.SyncSample = &mp4io.SyncSample{} } stream.timeScale = 90000 @@ -94,7 +96,6 @@ func (self *Muxer) newStream(codec av.CodecData) (err error) { 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() @@ -118,7 +119,29 @@ func (self *Stream) fillTrackAtom() (err error) { } self.trackAtom.Header.TrackWidth = float64(width) self.trackAtom.Header.TrackHeight = float64(height) - + } else if self.Type() == av.H265 { + codec := self.CodecData.(h264parser.CodecData) + width, height := codec.Width(), codec.Height() + self.sample.SampleDesc.HV1Desc = &mp4io.HV1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: int16(width), + Height: int16(height), + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + Conf: &mp4io.HV1Conf{Data: codec.AVCDecoderConfRecordBytes()}, + } + self.trackAtom.Media.Handler = &mp4io.HandlerRefer{ + SubType: [4]byte{'v', 'i', 'd', 'e'}, + Name: []byte("Video Media Handler"), + } + self.trackAtom.Media.Info.Video = &mp4io.VideoMediaInfo{ + Flags: 0x000001, + } + self.trackAtom.Header.TrackWidth = float64(width) + self.trackAtom.Header.TrackHeight = float64(height) } else if self.Type() == av.AAC { codec := self.CodecData.(aacparser.CodecData) self.sample.SampleDesc.MP4ADesc = &mp4io.MP4ADesc{ diff --git a/format/mp4f/muxer.go b/format/mp4f/muxer.go index 9b24833..a9dba18 100644 --- a/format/mp4f/muxer.go +++ b/format/mp4f/muxer.go @@ -34,7 +34,7 @@ func (self *Muxer) SetMaxFrames(count int) { } func (self *Muxer) newStream(codec av.CodecData) (err error) { switch codec.Type() { - case av.H264, av.AAC: + case av.H264, av.H265, av.AAC: default: err = fmt.Errorf("fmp4: codec type=%v is not supported", codec.Type()) return @@ -78,6 +78,9 @@ func (self *Muxer) newStream(codec av.CodecData) (err error) { case av.H264: stream.sample.SyncSample = &mp4io.SyncSample{} stream.timeScale = 90000 + case av.H265: + stream.sample.SyncSample = &mp4io.SyncSample{} + stream.timeScale = 90000 case av.AAC: stream.timeScale = int64(codec.(av.AudioCodecData).SampleRate()) } @@ -174,6 +177,28 @@ func (self *Stream) fillTrackAtom() (err error) { Flags: 0x000001, } self.codecString = fmt.Sprintf("avc1.%02X%02X%02X", codec.RecordInfo.AVCProfileIndication, codec.RecordInfo.ProfileCompatibility, codec.RecordInfo.AVCLevelIndication) + } else if self.Type() == av.H265 { + codec := self.CodecData.(h264parser.CodecData) + width, height := codec.Width(), codec.Height() + self.sample.SampleDesc.HV1Desc = &mp4io.HV1Desc{ + DataRefIdx: 1, + HorizontalResolution: 72, + VorizontalResolution: 72, + Width: int16(width), + Height: int16(height), + FrameCount: 1, + Depth: 24, + ColorTableId: -1, + Conf: &mp4io.HV1Conf{Data: codec.AVCDecoderConfRecordBytes()}, + } + 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("hvc1.%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{ @@ -183,8 +208,7 @@ func (self *Stream) fillTrackAtom() (err error) { SampleRate: float64(codec.SampleRate()), Unknowns: []mp4io.Atom{self.buildEsds(codec.MPEG4AudioConfigBytes())}, } - //log.Fatalln(codec.MPEG4AudioConfigBytes()) - //log.Fatalln(codec.SampleFormat().BytesPerSample()) + self.trackAtom.Header.Volume = 1 self.trackAtom.Header.AlternateGroup = 1 self.trackAtom.Header.Duration = 0