mirror of https://github.com/gwuhaolin/livego.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
6.4 KiB
279 lines
6.4 KiB
package hls |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"github.com/gwuhaolin/livego/configure" |
|
"time" |
|
|
|
"github.com/gwuhaolin/livego/av" |
|
"github.com/gwuhaolin/livego/container/flv" |
|
"github.com/gwuhaolin/livego/container/ts" |
|
"github.com/gwuhaolin/livego/parser" |
|
|
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
const ( |
|
videoHZ = 90000 |
|
aacSampleLen = 1024 |
|
maxQueueNum = 512 |
|
|
|
h264_default_hz uint64 = 90 |
|
) |
|
|
|
type Source struct { |
|
av.RWBaser |
|
seq int |
|
info av.Info |
|
bwriter *bytes.Buffer |
|
btswriter *bytes.Buffer |
|
demuxer *flv.Demuxer |
|
muxer *ts.Muxer |
|
pts, dts uint64 |
|
stat *status |
|
align *align |
|
cache *audioCache |
|
tsCache *TSCacheItem |
|
tsparser *parser.CodecParser |
|
closed bool |
|
packetQueue chan *av.Packet |
|
} |
|
|
|
func NewSource(info av.Info) *Source { |
|
info.Inter = true |
|
s := &Source{ |
|
info: info, |
|
align: &align{}, |
|
stat: newStatus(), |
|
RWBaser: av.NewRWBaser(time.Second * 10), |
|
cache: newAudioCache(), |
|
demuxer: flv.NewDemuxer(), |
|
muxer: ts.NewMuxer(), |
|
tsCache: NewTSCacheItem(info.Key), |
|
tsparser: parser.NewCodecParser(), |
|
bwriter: bytes.NewBuffer(make([]byte, 100*1024)), |
|
packetQueue: make(chan *av.Packet, maxQueueNum), |
|
} |
|
go func() { |
|
err := s.SendPacket() |
|
if err != nil { |
|
log.Warning("send packet error: ", err) |
|
s.closed = true |
|
} |
|
}() |
|
return s |
|
} |
|
|
|
func (source *Source) GetCacheInc() *TSCacheItem { |
|
return source.tsCache |
|
} |
|
|
|
func (source *Source) DropPacket(pktQue chan *av.Packet, info av.Info) { |
|
log.Warningf("[%v] packet queue max!!!", info) |
|
for i := 0; i < maxQueueNum-84; i++ { |
|
tmpPkt, ok := <-pktQue |
|
// try to don't drop audio |
|
if ok && tmpPkt.IsAudio { |
|
if len(pktQue) > maxQueueNum-2 { |
|
<-pktQue |
|
} else { |
|
pktQue <- tmpPkt |
|
} |
|
} |
|
|
|
if ok && tmpPkt.IsVideo { |
|
videoPkt, ok := tmpPkt.Header.(av.VideoPacketHeader) |
|
// dont't drop sps config and dont't drop key frame |
|
if ok && (videoPkt.IsSeq() || videoPkt.IsKeyFrame()) { |
|
pktQue <- tmpPkt |
|
} |
|
if len(pktQue) > maxQueueNum-10 { |
|
<-pktQue |
|
} |
|
} |
|
|
|
} |
|
log.Debug("packet queue len: ", len(pktQue)) |
|
} |
|
|
|
func (source *Source) Write(p *av.Packet) (err error) { |
|
err = nil |
|
if source.closed { |
|
err = fmt.Errorf("hls source closed") |
|
return |
|
} |
|
source.SetPreTime() |
|
defer func() { |
|
if e := recover(); e != nil { |
|
err = fmt.Errorf("hls source has already been closed:%v", e) |
|
} |
|
}() |
|
if len(source.packetQueue) >= maxQueueNum-24 { |
|
source.DropPacket(source.packetQueue, source.info) |
|
} else { |
|
if !source.closed { |
|
source.packetQueue <- p |
|
} |
|
} |
|
return |
|
} |
|
|
|
func (source *Source) SendPacket() error { |
|
defer func() { |
|
log.Debugf("[%v] hls sender stop", source.info) |
|
if r := recover(); r != nil { |
|
log.Warning("hls SendPacket panic: ", r) |
|
} |
|
}() |
|
|
|
log.Debugf("[%v] hls sender start", source.info) |
|
for { |
|
if source.closed { |
|
return fmt.Errorf("closed") |
|
} |
|
|
|
p, ok := <-source.packetQueue |
|
if ok { |
|
if p.IsMetadata { |
|
continue |
|
} |
|
|
|
err := source.demuxer.Demux(p) |
|
if err == flv.ErrAvcEndSEQ { |
|
log.Warning(err) |
|
continue |
|
} else { |
|
if err != nil { |
|
log.Warning(err) |
|
return err |
|
} |
|
} |
|
compositionTime, isSeq, err := source.parse(p) |
|
if err != nil { |
|
log.Warning(err) |
|
} |
|
if err != nil || isSeq { |
|
continue |
|
} |
|
if source.btswriter != nil { |
|
source.stat.update(p.IsVideo, p.TimeStamp) |
|
source.calcPtsDts(p.IsVideo, p.TimeStamp, uint32(compositionTime)) |
|
source.tsMux(p) |
|
} |
|
} else { |
|
return fmt.Errorf("closed") |
|
} |
|
} |
|
} |
|
|
|
func (source *Source) Info() (ret av.Info) { |
|
return source.info |
|
} |
|
|
|
func (source *Source) cleanup() { |
|
close(source.packetQueue) |
|
source.bwriter = nil |
|
source.btswriter = nil |
|
source.cache = nil |
|
source.tsCache = nil |
|
} |
|
|
|
func (source *Source) Close(err error) { |
|
log.Debug("hls source closed: ", source.info) |
|
if !source.closed && !configure.Config.GetBool("hls_keep_after_end") { |
|
source.cleanup() |
|
} |
|
source.closed = true |
|
} |
|
|
|
func (source *Source) cut() { |
|
newf := true |
|
if source.btswriter == nil { |
|
source.btswriter = bytes.NewBuffer(nil) |
|
} else if source.btswriter != nil && source.stat.durationMs() >= duration { |
|
source.flushAudio() |
|
|
|
source.seq++ |
|
filename := fmt.Sprintf("/%s/%d.ts", source.info.Key, time.Now().Unix()) |
|
item := NewTSItem(filename, int(source.stat.durationMs()), source.seq, source.btswriter.Bytes()) |
|
source.tsCache.SetItem(filename, item) |
|
|
|
source.btswriter.Reset() |
|
source.stat.resetAndNew() |
|
} else { |
|
newf = false |
|
} |
|
if newf { |
|
source.btswriter.Write(source.muxer.PAT()) |
|
source.btswriter.Write(source.muxer.PMT(av.SOUND_AAC, true)) |
|
} |
|
} |
|
|
|
func (source *Source) parse(p *av.Packet) (int32, bool, error) { |
|
var compositionTime int32 |
|
var ah av.AudioPacketHeader |
|
var vh av.VideoPacketHeader |
|
if p.IsVideo { |
|
vh = p.Header.(av.VideoPacketHeader) |
|
if vh.CodecID() != av.VIDEO_H264 { |
|
return compositionTime, false, ErrNoSupportVideoCodec |
|
} |
|
compositionTime = vh.CompositionTime() |
|
if vh.IsKeyFrame() && vh.IsSeq() { |
|
return compositionTime, true, source.tsparser.Parse(p, source.bwriter) |
|
} |
|
} else { |
|
ah = p.Header.(av.AudioPacketHeader) |
|
if ah.SoundFormat() != av.SOUND_AAC { |
|
return compositionTime, false, ErrNoSupportAudioCodec |
|
} |
|
if ah.AACPacketType() == av.AAC_SEQHDR { |
|
return compositionTime, true, source.tsparser.Parse(p, source.bwriter) |
|
} |
|
} |
|
source.bwriter.Reset() |
|
if err := source.tsparser.Parse(p, source.bwriter); err != nil { |
|
return compositionTime, false, err |
|
} |
|
p.Data = source.bwriter.Bytes() |
|
|
|
if p.IsVideo && vh.IsKeyFrame() { |
|
source.cut() |
|
} |
|
return compositionTime, false, nil |
|
} |
|
|
|
func (source *Source) calcPtsDts(isVideo bool, ts, compositionTs uint32) { |
|
source.dts = uint64(ts) * h264_default_hz |
|
if isVideo { |
|
source.pts = source.dts + uint64(compositionTs)*h264_default_hz |
|
} else { |
|
sampleRate, _ := source.tsparser.SampleRate() |
|
source.align.align(&source.dts, uint32(videoHZ*aacSampleLen/sampleRate)) |
|
source.pts = source.dts |
|
} |
|
} |
|
func (source *Source) flushAudio() error { |
|
return source.muxAudio(1) |
|
} |
|
|
|
func (source *Source) muxAudio(limit byte) error { |
|
if source.cache.CacheNum() < limit { |
|
return nil |
|
} |
|
var p av.Packet |
|
_, pts, buf := source.cache.GetFrame() |
|
p.Data = buf |
|
p.TimeStamp = uint32(pts / h264_default_hz) |
|
return source.muxer.Mux(&p, source.btswriter) |
|
} |
|
|
|
func (source *Source) tsMux(p *av.Packet) error { |
|
if p.IsVideo { |
|
return source.muxer.Mux(p, source.btswriter) |
|
} else { |
|
source.cache.Cache(p.Data, source.pts) |
|
return source.muxAudio(cache_max_frames) |
|
} |
|
}
|
|
|