mirror of
https://github.com/lkmio/lkm.git
synced 2025-09-27 03:26:01 +08:00
382 lines
11 KiB
Go
382 lines
11 KiB
Go
package stream
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/lkmio/avformat/utils"
|
|
"github.com/lkmio/lkm/log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// SourceType 推流类型
|
|
type SourceType byte
|
|
|
|
// TransStreamProtocol 输出的流协议
|
|
type TransStreamProtocol uint32
|
|
|
|
// SessionState 推拉流Session的状态
|
|
type SessionState uint32
|
|
|
|
const (
|
|
SourceTypeRtmp = SourceType(1)
|
|
SourceType28181 = SourceType(2)
|
|
SourceType1078 = SourceType(3)
|
|
SourceTypeGBTalk = SourceType(4) // 国标广播/对讲
|
|
|
|
TransStreamRtmp = TransStreamProtocol(1)
|
|
TransStreamFlv = TransStreamProtocol(2)
|
|
TransStreamRtsp = TransStreamProtocol(3)
|
|
TransStreamHls = TransStreamProtocol(4)
|
|
TransStreamRtc = TransStreamProtocol(5)
|
|
TransStreamGBCascadedForward = TransStreamProtocol(6) // 国标级联转发
|
|
TransStreamGBTalkForward = TransStreamProtocol(7) // 国标广播/对讲转发
|
|
)
|
|
|
|
const (
|
|
SessionStateCreated = SessionState(1) // 新建状态
|
|
SessionStateHandshaking = SessionState(2) // 握手中
|
|
SessionStateHandshakeFailure = SessionState(3) // 握手失败
|
|
SessionStateHandshakeSuccess = SessionState(4) // 握手完成
|
|
SessionStateWaiting = SessionState(5) // 位于等待队列中
|
|
SessionStateTransferring = SessionState(6) // 推拉流中
|
|
SessionStateClosed = SessionState(7) // 关闭状态
|
|
)
|
|
|
|
func (s SourceType) String() string {
|
|
if SourceTypeRtmp == s {
|
|
return "rtmp"
|
|
} else if SourceType28181 == s {
|
|
return "28181"
|
|
} else if SourceType1078 == s {
|
|
return "jt1078"
|
|
} else if SourceTypeGBTalk == s {
|
|
return "gb_talk"
|
|
}
|
|
|
|
panic(fmt.Sprintf("unknown source type %d", s))
|
|
}
|
|
|
|
func (p TransStreamProtocol) String() string {
|
|
if TransStreamRtmp == p {
|
|
return "rtmp"
|
|
} else if TransStreamFlv == p {
|
|
return "flv"
|
|
} else if TransStreamRtsp == p {
|
|
return "rtsp"
|
|
} else if TransStreamHls == p {
|
|
return "hls"
|
|
} else if TransStreamRtc == p {
|
|
return "rtc"
|
|
} else if TransStreamGBCascadedForward == p {
|
|
return "gb_cascaded_forward"
|
|
} else if TransStreamGBTalkForward == p {
|
|
return "gb_talk_forward"
|
|
}
|
|
|
|
panic(fmt.Sprintf("unknown stream protocol %d", p))
|
|
}
|
|
|
|
func (s SessionState) String() string {
|
|
if SessionStateCreated == s {
|
|
return "create"
|
|
} else if SessionStateHandshaking == s {
|
|
return "handshaking"
|
|
} else if SessionStateHandshakeFailure == s {
|
|
return "handshake failure"
|
|
} else if SessionStateHandshakeSuccess == s {
|
|
return "handshake success"
|
|
} else if SessionStateWaiting == s {
|
|
return "waiting"
|
|
} else if SessionStateTransferring == s {
|
|
return "transferring"
|
|
} else if SessionStateClosed == s {
|
|
return "closed"
|
|
}
|
|
|
|
panic(fmt.Sprintf("unknown session state %d", s))
|
|
}
|
|
|
|
func Path2SourceID(path string, suffix string) (string, error) {
|
|
source := strings.TrimSpace(path)
|
|
if strings.HasPrefix(source, "/") {
|
|
source = source[1:]
|
|
}
|
|
|
|
if len(suffix) > 0 && strings.HasSuffix(source, suffix) {
|
|
source = source[:len(source)-len(suffix)]
|
|
}
|
|
|
|
source = strings.TrimSpace(source)
|
|
|
|
if len(strings.TrimSpace(source)) == 0 {
|
|
return "", fmt.Errorf("the request source cannot be empty")
|
|
}
|
|
|
|
return source, nil
|
|
}
|
|
|
|
// ParseUrl 从推拉流url中解析出流id和url参数
|
|
func ParseUrl(name string) (string, url.Values) {
|
|
index := strings.Index(name, "?")
|
|
if index > 0 && index < len(name)-1 {
|
|
query, err := url.ParseQuery(name[index+1:])
|
|
if err != nil {
|
|
log.Sugar.Errorf("解析url参数失败 err:%s url:%s", err.Error(), name)
|
|
return name, nil
|
|
}
|
|
|
|
return name[:index], query
|
|
}
|
|
|
|
return name, nil
|
|
}
|
|
|
|
//
|
|
//func ExtractVideoPacket(codec utils.AVCodecID, key, extractStream bool, data []byte, pts, dts int64, index, timebase int) (*avformat.AVStream, *avformat.AVPacket, error) {
|
|
// var stream *avformat.AVStream
|
|
//
|
|
// if utils.AVCodecIdH264 == codec {
|
|
// //从关键帧中解析出sps和pps
|
|
// if key && extractStream {
|
|
// sps, pps, err := avc.ParseExtraDataFromKeyNALU(data)
|
|
// if err != nil {
|
|
// log.Sugar.Errorf("从关键帧中解析sps pps失败 data:%s", hex.EncodeToString(data))
|
|
// return nil, nil, err
|
|
// }
|
|
//
|
|
// codecData, err := utils.NewAVCCodecData(sps, pps)
|
|
// if err != nil {
|
|
// log.Sugar.Errorf("解析sps pps失败 data:%s sps:%s, pps:%s", hex.EncodeToString(data), hex.EncodeToString(sps), hex.EncodeToString(pps))
|
|
// return nil, nil, err
|
|
// }
|
|
//
|
|
// stream = avformat.NewAVStream(utils.AVMediaTypeVideo, 0, codec, codecData.AnnexBExtraData(), codecData)
|
|
// }
|
|
//
|
|
// } else if utils.AVCodecIdH265 == codec {
|
|
// if key && extractStream {
|
|
// vps, sps, pps, err := hevc.ParseExtraDataFromKeyNALU(data)
|
|
// if err != nil {
|
|
// log.Sugar.Errorf("从关键帧中解析vps sps pps失败 data:%s", hex.EncodeToString(data))
|
|
// return nil, nil, err
|
|
// }
|
|
//
|
|
// codecData, err := utils.NewHEVCCodecData(vps, sps, pps)
|
|
// if err != nil {
|
|
// log.Sugar.Errorf("解析sps pps失败 data:%s vps:%s sps:%s, pps:%s", hex.EncodeToString(data), hex.EncodeToString(vps), hex.EncodeToString(sps), hex.EncodeToString(pps))
|
|
// return nil, nil, err
|
|
// }
|
|
//
|
|
// stream = avformat.NewAVStream(utils.AVMediaTypeVideo, 0, codec, codecData.AnnexBExtraData(), codecData)
|
|
// }
|
|
//
|
|
// }
|
|
//
|
|
// packet := avformat.NewVideoPacket(data, dts, pts, key, utils.PacketTypeAnnexB, codec, index, timebase)
|
|
// return stream, packet, nil
|
|
//}
|
|
//
|
|
//func ExtractAudioPacket(codec utils.AVCodecID, extractStream bool, data []byte, pts, dts int64, index, timebase int) (*avformat.AVStream, *avformat.AVPacket, error) {
|
|
// var stream *avformat.AVStream
|
|
// var packet *avformat.AVPacket
|
|
// if utils.AVCodecIdAAC == codec {
|
|
// //必须包含ADTSHeader
|
|
// if len(data) < 7 {
|
|
// return nil, nil, fmt.Errorf("need more data")
|
|
// }
|
|
//
|
|
// var skip int
|
|
// header, err := utils.ReadADtsFixedHeader(data)
|
|
// if err != nil {
|
|
// log.Sugar.Errorf("读取ADTSHeader失败 data:%s", hex.EncodeToString(data[:7]))
|
|
// return nil, nil, err
|
|
// } else {
|
|
// skip = 7
|
|
// //跳过ADtsHeader长度
|
|
// if header.ProtectionAbsent() == 0 {
|
|
// skip += 2
|
|
// }
|
|
// }
|
|
//
|
|
// if extractStream {
|
|
// configData, err := utils.ADtsHeader2MpegAudioConfigData(header)
|
|
// config, err := utils.ParseMpeg4AudioConfig(configData)
|
|
// println(config)
|
|
// if err != nil {
|
|
// log.Sugar.Errorf("adt头转m4ac失败 data:%s", hex.EncodeToString(data[:7]))
|
|
// return nil, nil, err
|
|
// }
|
|
//
|
|
// stream = avformat.NewAVStream(utils.AVMediaTypeAudio, index, codec, configData, nil)
|
|
// }
|
|
//
|
|
// packet = utils.NewAudioPacket(data[skip:], dts, pts, codec, index, timebase)
|
|
// } else if utils.AVCodecIdPCMALAW == codec || utils.AVCodecIdPCMMULAW == codec {
|
|
// if extractStream {
|
|
// stream = avformat.NewAVStream(utils.AVMediaTypeAudio, index, codec, nil, nil)
|
|
// }
|
|
//
|
|
// packet = utils.NewAudioPacket(data, dts, pts, codec, index, timebase)
|
|
// }
|
|
//
|
|
// return stream, packet, nil
|
|
//}
|
|
|
|
// StartReceiveDataTimer 启动收流超时计时器
|
|
// 收流超时, 客观上认为是流中断, 应该关闭Source. 如果开启了Hook, 并且Hook返回200应答, 则不关闭Source.
|
|
func StartReceiveDataTimer(source Source) *time.Timer {
|
|
utils.Assert(AppConfig.ReceiveTimeout > 0)
|
|
|
|
var receiveDataTimer *time.Timer
|
|
receiveDataTimer = time.AfterFunc(time.Duration(AppConfig.ReceiveTimeout), func() {
|
|
dis := time.Now().Sub(source.LastPacketTime())
|
|
|
|
if dis >= time.Duration(AppConfig.ReceiveTimeout) {
|
|
log.Sugar.Errorf("收流超时 source: %s", source.GetID())
|
|
|
|
var shouldClose = true
|
|
if AppConfig.Hooks.IsEnableOnReceiveTimeout() {
|
|
// 此处参考返回值err, 客观希望关闭Source
|
|
response, err := HookReceiveTimeoutEvent(source)
|
|
shouldClose = !(err == nil && response != nil && http.StatusOK == response.StatusCode)
|
|
}
|
|
|
|
if shouldClose {
|
|
source.Close()
|
|
return
|
|
}
|
|
}
|
|
|
|
// 对精度没要求
|
|
receiveDataTimer.Reset(time.Duration(AppConfig.ReceiveTimeout))
|
|
})
|
|
|
|
return receiveDataTimer
|
|
}
|
|
|
|
// StartIdleTimer 启动拉流空闲计时器
|
|
// 拉流空闲, 不应该关闭Source. 如果开启了Hook, 并且Hook返回非200应答, 则关闭Source.
|
|
func StartIdleTimer(source Source) *time.Timer {
|
|
utils.Assert(AppConfig.IdleTimeout > 0)
|
|
utils.Assert(AppConfig.Hooks.IsEnableOnIdleTimeout())
|
|
|
|
var idleTimer *time.Timer
|
|
idleTimer = time.AfterFunc(time.Duration(AppConfig.IdleTimeout), func() {
|
|
dis := time.Now().Sub(source.LastStreamEndTime())
|
|
|
|
if source.SinkCount() < 1 && dis >= time.Duration(AppConfig.IdleTimeout) {
|
|
log.Sugar.Errorf("拉流空闲超时 source: %s", source.GetID())
|
|
|
|
// 此处不参考返回值err, 客观希望不关闭Source
|
|
response, _ := HookIdleTimeoutEvent(source)
|
|
if response != nil && http.StatusOK != response.StatusCode {
|
|
source.Close()
|
|
return
|
|
}
|
|
}
|
|
|
|
idleTimer.Reset(time.Duration(AppConfig.IdleTimeout))
|
|
})
|
|
|
|
return idleTimer
|
|
}
|
|
|
|
// LoopEvent 循环读取事件
|
|
func LoopEvent(source Source) {
|
|
// 将超时计时器放在此处开启, 方便在退出的时候关闭
|
|
var receiveTimer *time.Timer
|
|
var idleTimer *time.Timer
|
|
var probeTimer *time.Timer
|
|
|
|
defer func() {
|
|
log.Sugar.Debugf("主协程执行结束 source: %s", source.GetID())
|
|
|
|
// 关闭计时器
|
|
if receiveTimer != nil {
|
|
receiveTimer.Stop()
|
|
}
|
|
|
|
if idleTimer != nil {
|
|
idleTimer.Stop()
|
|
}
|
|
|
|
if probeTimer != nil {
|
|
probeTimer.Stop()
|
|
}
|
|
|
|
// 未使用的数据, 放回池中
|
|
for len(source.StreamPipe()) > 0 {
|
|
data := <-source.StreamPipe()
|
|
if size := cap(data); size > UDPReceiveBufferSize {
|
|
TCPReceiveBufferPool.Put(data[:size])
|
|
} else {
|
|
UDPReceiveBufferPool.Put(data[:size])
|
|
}
|
|
}
|
|
}()
|
|
|
|
// 开启收流超时计时器
|
|
if AppConfig.ReceiveTimeout > 0 {
|
|
receiveTimer = StartReceiveDataTimer(source)
|
|
}
|
|
|
|
// 开启拉流空闲超时计时器
|
|
if AppConfig.Hooks.IsEnableOnIdleTimeout() && AppConfig.IdleTimeout > 0 {
|
|
idleTimer = StartIdleTimer(source)
|
|
}
|
|
|
|
// 开启探测超时计时器
|
|
probeTimer = time.AfterFunc(time.Duration(AppConfig.ProbeTimeout)*time.Millisecond, func() {
|
|
if source.IsCompleted() {
|
|
return
|
|
}
|
|
|
|
var ok bool
|
|
source.ExecuteSyncEvent(func() {
|
|
source.ProbeTimeout()
|
|
ok = len(source.OriginTracks()) > 0
|
|
})
|
|
|
|
if !ok {
|
|
source.Close()
|
|
return
|
|
}
|
|
})
|
|
|
|
for {
|
|
select {
|
|
// 读取推流数据
|
|
case data := <-source.StreamPipe():
|
|
if AppConfig.ReceiveTimeout > 0 {
|
|
source.SetLastPacketTime(time.Now())
|
|
}
|
|
|
|
if err := source.Input(data); err != nil {
|
|
log.Sugar.Errorf("解析推流数据发生err: %s 释放source: %s", err.Error(), source.GetID())
|
|
go source.Close()
|
|
return
|
|
}
|
|
|
|
// 使用后, 放回池中
|
|
if size := cap(data); size > UDPReceiveBufferSize {
|
|
TCPReceiveBufferPool.Put(data[:size])
|
|
} else {
|
|
UDPReceiveBufferPool.Put(data[:size])
|
|
}
|
|
break
|
|
// 切换到主协程,执行该函数. 目的是用于无锁化处理推拉流的连接与断开, 推流源断开, 查询推流源信息等事件. 不要做耗时操作, 否则会影响推拉流.
|
|
case event := <-source.MainContextEvents():
|
|
event()
|
|
|
|
if source.IsClosed() {
|
|
// 处理推流管道剩余的数据?
|
|
return
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|