github.com/cnotch/ipchub@v1.1.0/av/format/flv/muxer.go (about) 1 // Copyright (c) 2019,CAOHONGJU All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 package flv 6 7 import ( 8 "fmt" 9 "runtime/debug" 10 "time" 11 12 "github.com/cnotch/ipchub/av/codec" 13 "github.com/cnotch/ipchub/av/format/amf" 14 "github.com/cnotch/queue" 15 "github.com/cnotch/xlog" 16 ) 17 18 // Packetizer 封包器 19 type Packetizer interface { 20 PacketizeSequenceHeader() error 21 Packetize(frame *codec.Frame) error 22 } 23 24 type emptyPacketizer struct{} 25 26 func (emptyPacketizer) PacketizeSequenceHeader() error { return nil } 27 func (emptyPacketizer) Packetize(frame *codec.Frame) error { return nil } 28 29 // Muxer flv muxer from av.Frame(H264[+AAC]) 30 type Muxer struct { 31 videoMeta *codec.VideoMeta 32 audioMeta *codec.AudioMeta 33 vp Packetizer 34 ap Packetizer 35 typeFlags byte 36 recvQueue *queue.SyncQueue 37 tagWriter TagWriter 38 closed bool 39 40 logger *xlog.Logger // 日志对象 41 } 42 43 // NewMuxer . 44 func NewMuxer(videoMeta *codec.VideoMeta, audioMeta *codec.AudioMeta, tagWriter TagWriter, logger *xlog.Logger) (*Muxer, error) { 45 muxer := &Muxer{ 46 recvQueue: queue.NewSyncQueue(), 47 videoMeta: videoMeta, 48 audioMeta: audioMeta, 49 vp: emptyPacketizer{}, 50 ap: emptyPacketizer{}, 51 typeFlags: byte(TypeFlagsVideo), 52 tagWriter: tagWriter, 53 closed: false, 54 logger: logger, 55 } 56 switch videoMeta.Codec { 57 case "H264": 58 muxer.vp = NewH264Packetizer(videoMeta, tagWriter) 59 case "H265": 60 muxer.vp = NewH265Packetizer(videoMeta, tagWriter) 61 default: 62 return nil, fmt.Errorf("flv muxer unsupport video codec type:%s", videoMeta.Codec) 63 } 64 65 if audioMeta.Codec == "AAC" { 66 muxer.typeFlags |= TypeFlagsAudio 67 muxer.ap = NewAacPacketizer(audioMeta, tagWriter) 68 } 69 70 go muxer.process() 71 return muxer, nil 72 } 73 74 // WriteFrame . 75 func (muxer *Muxer) WriteFrame(frame *codec.Frame) error { 76 muxer.recvQueue.Push(frame) 77 return nil 78 } 79 80 // Close . 81 func (muxer *Muxer) Close() error { 82 if muxer.closed { 83 return nil 84 } 85 86 muxer.closed = true 87 muxer.recvQueue.Signal() 88 return nil 89 } 90 91 // TypeFlags 返回 flv header 中的 TypeFlags 92 func (muxer *Muxer) TypeFlags() byte { 93 return muxer.typeFlags 94 } 95 96 func (muxer *Muxer) process() { 97 defer func() { 98 defer func() { // 避免 handler 再 panic 99 recover() 100 }() 101 102 if r := recover(); r != nil { 103 muxer.logger.Errorf("flvmuxer routine panic;r = %v \n %s", r, debug.Stack()) 104 } 105 106 // 尽早通知GC,回收内存 107 muxer.recvQueue.Reset() 108 }() 109 110 var packSequenceHeader bool 111 112 for !muxer.closed { 113 f := muxer.recvQueue.Pop() 114 if f == nil { 115 if !muxer.closed { 116 muxer.logger.Warn("flvmuxer:receive nil frame") 117 } 118 continue 119 } 120 121 if !packSequenceHeader{ 122 muxer.muxMetadataTag() 123 muxer.vp.PacketizeSequenceHeader() 124 muxer.ap.PacketizeSequenceHeader() 125 packSequenceHeader = true 126 } 127 128 frame := f.(*codec.Frame) 129 130 switch frame.MediaType { 131 case codec.MediaTypeVideo: 132 if err := muxer.vp.Packetize(frame); err != nil { 133 muxer.logger.Errorf("flvmuxer: muxVideoTag error - %s", err.Error()) 134 } 135 case codec.MediaTypeAudio: 136 if err := muxer.ap.Packetize(frame); err != nil { 137 muxer.logger.Errorf("flvmuxer: muxAudioTag error - %s", err.Error()) 138 } 139 default: 140 } 141 } 142 } 143 144 func (muxer *Muxer) muxMetadataTag() error { 145 properties := make(amf.EcmaArray, 0, 12) 146 147 properties = append(properties, 148 amf.ObjectProperty{ 149 Name: "creator", 150 Value: "ipchub stream media server"}) 151 properties = append(properties, 152 amf.ObjectProperty{ 153 Name: MetaDataCreationDate, 154 Value: time.Now().Format(time.RFC3339)}) 155 156 if muxer.typeFlags&TypeFlagsAudio > 0 { 157 properties = append(properties, 158 amf.ObjectProperty{ 159 Name: MetaDataAudioCodecID, 160 Value: SoundFormatAAC}) 161 properties = append(properties, 162 amf.ObjectProperty{ 163 Name: MetaDataAudioDateRate, 164 Value: muxer.audioMeta.DataRate}) 165 properties = append(properties, 166 amf.ObjectProperty{ 167 Name: MetaDataAudioSampleRate, 168 Value: muxer.audioMeta.SampleRate}) 169 properties = append(properties, 170 amf.ObjectProperty{ 171 Name: MetaDataAudioSampleSize, 172 Value: muxer.audioMeta.SampleSize}) 173 properties = append(properties, 174 amf.ObjectProperty{ 175 Name: MetaDataStereo, 176 Value: muxer.audioMeta.Channels > 1}) 177 } 178 179 vcodecID := CodecIDAVC 180 if muxer.videoMeta.Codec == "H265" { 181 vcodecID = CodecIDHEVC 182 } 183 184 properties = append(properties, 185 amf.ObjectProperty{ 186 Name: MetaDataVideoCodecID, 187 Value: vcodecID}) 188 properties = append(properties, 189 amf.ObjectProperty{ 190 Name: MetaDataVideoDataRate, 191 Value: muxer.videoMeta.DataRate}) 192 properties = append(properties, 193 amf.ObjectProperty{ 194 Name: MetaDataFrameRate, 195 Value: muxer.videoMeta.FrameRate}) 196 properties = append(properties, 197 amf.ObjectProperty{ 198 Name: MetaDataWidth, 199 Value: muxer.videoMeta.Width}) 200 properties = append(properties, 201 amf.ObjectProperty{ 202 Name: MetaDataHeight, 203 Value: muxer.videoMeta.Height}) 204 205 scriptData := ScriptData{ 206 Name: ScriptOnMetaData, 207 Value: properties, 208 } 209 data, _ := scriptData.Marshal() 210 211 tag := &Tag{ 212 TagType: TagTypeAmf0Data, 213 DataSize: uint32(len(data)), 214 Timestamp: 0, 215 StreamID: 0, 216 Data: data, 217 } 218 219 return muxer.tagWriter.WriteFlvTag(tag) 220 }