github.com/cnotch/ipchub@v1.1.0/av/format/hls/segmentgenerator.go (about) 1 // Copyright calabashdad. https://github.com/calabashdad/seal.git 2 // 3 // Copyright (c) 2019,CAOHONGJU All rights reserved. 4 // Use of this source code is governed by a MIT-style 5 // license that can be found in the LICENSE file. 6 7 package hls 8 9 import ( 10 "bytes" 11 "fmt" 12 "path/filepath" 13 "strconv" 14 15 "github.com/cnotch/ipchub/av/format/mpegts" 16 "github.com/cnotch/ipchub/utils/murmur" 17 "github.com/cnotch/xlog" 18 ) 19 20 // drop the segment when duration of ts too small. 21 const hlsSegmentMinDurationMs = 100 22 23 // in ms, for HLS aac flush the audio 24 const hlsAacDelay = 100 25 26 // SegmentGenerator generate the HLS ts segment. 27 type SegmentGenerator struct { 28 playlist *Playlist // 播放列表 29 path string // 流路径 30 hlsFragment int // 每个片段长度 31 32 memory bool // 使用内存存储缓存到硬盘 33 segmentPath string // 缓存文件路径 34 35 sequenceNo int // 片段序号 36 current *segment //current segment 37 38 logger *xlog.Logger 39 40 audioRate int 41 afCache *mpegts.Frame // audio frame cache 42 afCacheBuff bytes.Buffer 43 // time jitter for aac 44 aacJitter *hlsAacJitter 45 } 46 47 // NewSegmentGenerator . 48 func NewSegmentGenerator(playlist *Playlist, path string, hlsFragment int, segmentPath string, audioRate int, logger *xlog.Logger) (*SegmentGenerator, error) { 49 sg := &SegmentGenerator{ 50 playlist: playlist, 51 path: path, 52 hlsFragment: hlsFragment, 53 memory: segmentPath == "", 54 segmentPath: segmentPath, 55 logger: logger, 56 sequenceNo: 0, 57 audioRate: audioRate, 58 aacJitter: newHlsAacJitter(), 59 } 60 61 if err := sg.segmentOpen(0); err != nil { 62 return nil, err 63 } 64 65 // set the current segment to sequence header, 66 // when close the segement, it will write a discontinuity to m3u8 file. 67 sg.current.isSequenceHeader = true 68 return sg, nil 69 } 70 71 // open a new segment, a new ts file 72 // segmentStartDts use to calc the segment duration, use 0 for the first segment of hls 73 func (sg *SegmentGenerator) segmentOpen(segmentStartDts int64) (err error) { 74 if nil != sg.current { 75 // has already opened, ignore segment open 76 return 77 } 78 79 // new segment 80 sg.sequenceNo++ 81 curr := newSegment(sg.memory) 82 curr.sequenceNo = sg.sequenceNo 83 curr.segmentStartPts = segmentStartDts 84 curr.uri = "/streams" + sg.path + "/" + strconv.Itoa(sg.sequenceNo) + ".ts" 85 86 tsFileName := fmt.Sprintf("%d_%d.ts", murmur.OfString(sg.path), curr.sequenceNo) 87 tsFilePath := filepath.Join(sg.segmentPath, tsFileName) 88 if err = curr.file.open(tsFilePath); err != nil { 89 return 90 } 91 92 sg.current = curr 93 return 94 } 95 96 // WriteMpegtsFrame implements mpegts.FrameWriter 97 func (sg *SegmentGenerator) WriteMpegtsFrame(frame *mpegts.Frame) (err error) { 98 // if current is NULL, segment is not open, ignore the flush event. 99 if nil == sg.current { 100 return 101 } 102 if len(frame.Payload) <= 0 { 103 return 104 } 105 106 if frame.IsAudio() { 107 if sg.afCache == nil { 108 pts := sg.aacJitter.onBufferStart(frame.Pts, sg.audioRate) 109 headerFrame := *frame 110 headerFrame.Dts = pts 111 headerFrame.Pts = pts 112 sg.afCache = &headerFrame 113 sg.afCacheBuff.Write(frame.Payload) 114 } else { 115 sg.afCacheBuff.Write(frame.Header) 116 sg.afCacheBuff.Write(frame.Payload) 117 sg.aacJitter.onBufferContinue() 118 } 119 120 if frame.Pts-sg.afCache.Pts > hlsAacDelay*90 { 121 return sg.flushAudioCache() 122 } 123 124 // reap when current source is pure audio. 125 // it maybe changed when stream info changed, 126 // for example, pure audio when start, audio/video when publishing, 127 // pure audio again for audio disabled. 128 // so we reap event when the audio incoming when segment overflow. 129 // we use absolutely overflow of segment to make jwplayer/ffplay happy 130 if sg.isSegmentAbsolutelyOverflow() { 131 if err = sg.reapSegment(frame.Pts); err != nil { 132 return 133 } 134 } 135 return 136 } 137 138 if frame.IsKeyFrame() && sg.isSegmentOverflow() { 139 if err = sg.reapSegment(frame.Pts); err != nil { 140 return 141 } 142 } 143 144 // flush video when got one 145 if err = sg.flushFrame(frame); err != nil { 146 return 147 } 148 return 149 } 150 151 func (sg *SegmentGenerator) flushAudioCache() (err error) { 152 if sg.afCache == nil { 153 return 154 } 155 156 sg.afCache.Payload = sg.afCacheBuff.Bytes() 157 err = sg.flushFrame(sg.afCache) 158 sg.afCache = nil 159 sg.afCacheBuff.Reset() 160 return 161 } 162 163 func (sg *SegmentGenerator) flushFrame(frame *mpegts.Frame) (err error) { 164 sg.current.updateDuration(frame.Pts) 165 if err = sg.current.file.writeFrame(frame); err != nil { 166 return 167 } 168 return 169 } 170 171 // close segment(ts) 172 func (sg *SegmentGenerator) segmentClose() (err error) { 173 if nil == sg.current { 174 return 175 } 176 177 curr := sg.current 178 sg.current = nil 179 curr.file.close() 180 if curr.duration*1000 < hlsSegmentMinDurationMs { 181 // reuse current segment index 182 sg.sequenceNo-- 183 curr.file.delete() 184 } else { 185 sg.playlist.addSegment(curr) 186 } 187 return 188 } 189 190 // reopen the sg for a new hls segment, 191 // close current segment, open a new segment, 192 // then write the key frame to the new segment. 193 // so, user must reap_segment then flush_video to hls sg. 194 func (sg *SegmentGenerator) reapSegment(segmentStartDts int64) (err error) { 195 if err = sg.segmentClose(); err != nil { 196 return 197 } 198 199 if err = sg.segmentOpen(segmentStartDts); err != nil { 200 return 201 } 202 203 // segment open, flush the audio. 204 // @see: ngx_rtmp_hls_open_fragment 205 /* start fragment with audio to make iPhone happy */ 206 err = sg.flushAudioCache() 207 208 return 209 } 210 211 // whether segment overflow, 212 // that is whether the current segment duration>=(the segment in config) 213 func (sg *SegmentGenerator) isSegmentOverflow() bool { 214 return sg.current.duration >= float64(sg.hlsFragment) 215 } 216 217 // whether segment absolutely overflow, for pure audio to reap segment, 218 // that is whether the current segment duration>=2*(the segment in config) 219 func (sg *SegmentGenerator) isSegmentAbsolutelyOverflow() bool { 220 if nil == sg.current { 221 return true 222 } 223 224 res := sg.current.duration >= float64(2*sg.hlsFragment) 225 226 return res 227 } 228 229 // Close . 230 func (sg *SegmentGenerator) Close() error { 231 if nil == sg.current { 232 return nil 233 } 234 235 curr := sg.current 236 sg.current = nil 237 curr.file.close() 238 curr.file.delete() 239 return nil 240 }