mirror of
https://github.com/langhuihui/monibuca.git
synced 2026-05-09 15:31:07 +08:00
282 lines
7.4 KiB
Go
282 lines
7.4 KiB
Go
package plugin_gb28181pro
|
|
|
|
import (
|
|
"fmt"
|
|
sipgo "github.com/emiago/sipgo"
|
|
"github.com/emiago/sipgo/sip"
|
|
m7s "m7s.live/v5"
|
|
"m7s.live/v5/pkg/task"
|
|
"m7s.live/v5/pkg/util"
|
|
gb28181 "m7s.live/v5/plugin/gb28181/pkg"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ForwardDialog 是用于转发RTP流的会话结构体
|
|
type ForwardDialog struct {
|
|
task.Job
|
|
channel *Channel
|
|
gb28181.InviteOptions
|
|
gb *GB28181Plugin
|
|
session *sipgo.DialogClientSession
|
|
pullCtx m7s.PullJob
|
|
forwarder *gb28181.RTPForwarder
|
|
platformIP string
|
|
platformPort int
|
|
platformSSRC string // 上级平台的SSRC
|
|
platformCallId string //上级平台发起invite的callid
|
|
// 是否为TCP传输
|
|
TCP bool
|
|
// 是否为TCP主动模式
|
|
TCPActive bool
|
|
start int64
|
|
end int64
|
|
}
|
|
|
|
// GetCallID 获取会话的CallID
|
|
func (d *ForwardDialog) GetCallID() string {
|
|
return d.session.InviteRequest.CallID().Value()
|
|
}
|
|
|
|
// GetPullJob 获取拉流任务
|
|
func (d *ForwardDialog) GetPullJob() *m7s.PullJob {
|
|
return &d.pullCtx
|
|
}
|
|
|
|
// GetKey 获取会话标识符
|
|
func (d *ForwardDialog) GetKey() uint32 {
|
|
return d.SSRC
|
|
}
|
|
|
|
// Start 启动会话
|
|
func (d *ForwardDialog) Start() (err error) {
|
|
// 处理时间范围
|
|
isLive := true
|
|
if d.start > 0 && d.end > 0 {
|
|
isLive = false
|
|
d.pullCtx.PublishConfig.PubType = m7s.PublishTypeVod
|
|
}
|
|
//err = d.pullCtx.Publish()
|
|
if err != nil {
|
|
return
|
|
}
|
|
sss := strings.Split(d.pullCtx.RemoteURL, "/")
|
|
deviceId, channelId := sss[0], sss[1]
|
|
var device *Device
|
|
if deviceTmp, ok := d.gb.devices.Get(deviceId); ok {
|
|
device = deviceTmp
|
|
if channel, ok := deviceTmp.channels.Get(channelId); ok {
|
|
d.channel = channel
|
|
} else {
|
|
return fmt.Errorf("channel %s not found", channelId)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("device %s not found", deviceId)
|
|
}
|
|
|
|
// 注册对话到集合,使用类型转换
|
|
d.gb.forwardDialogs.Set(d)
|
|
//defer d.gb.forwardDialogs.Remove(d)
|
|
|
|
if d.gb.MediaPort.Valid() {
|
|
select {
|
|
case d.MediaPort = <-d.gb.tcpPorts:
|
|
defer func() {
|
|
d.gb.tcpPorts <- d.MediaPort
|
|
}()
|
|
default:
|
|
return fmt.Errorf("no available tcp port")
|
|
}
|
|
} else {
|
|
d.MediaPort = d.gb.MediaPort[0]
|
|
}
|
|
|
|
// 使用上级平台的SSRC(如果有)或者设备的CreateSSRC方法
|
|
var ssrcValue uint16
|
|
if d.platformSSRC != "" {
|
|
// 使用上级平台的SSRC
|
|
if ssrcInt, err := strconv.ParseUint(d.platformSSRC, 10, 32); err == nil {
|
|
d.SSRC = uint32(ssrcInt)
|
|
} else {
|
|
d.gb.Error("parse platform ssrc error", "err", err)
|
|
// 使用设备的CreateSSRC方法作为备选
|
|
ssrcValue = device.CreateSSRC(d.gb.Serial)
|
|
d.SSRC = uint32(ssrcValue)
|
|
}
|
|
} else {
|
|
// 使用设备的CreateSSRC方法
|
|
ssrcValue = device.CreateSSRC(d.gb.Serial)
|
|
d.SSRC = uint32(ssrcValue)
|
|
}
|
|
|
|
// 获取 SDP IP
|
|
sdpIP := device.LocalIP
|
|
if sdpIP == "" {
|
|
sdpIP = device.mediaIp
|
|
}
|
|
|
|
// 构建 SDP 内容
|
|
sdpInfo := []string{
|
|
"v=0",
|
|
fmt.Sprintf("o=%s 0 0 IN IP4 %s", device.DeviceID, sdpIP),
|
|
fmt.Sprintf("s=%s", util.Conditional(isLive, "Play", "Playback")), // 根据是否有时间参数决定
|
|
}
|
|
|
|
// 非直播模式下添加u行,保持在s=和c=之间
|
|
if !isLive {
|
|
sdpInfo = append(sdpInfo, fmt.Sprintf("u=%s:0", channelId))
|
|
}
|
|
|
|
// 添加c行
|
|
sdpInfo = append(sdpInfo, "c=IN IP4 "+sdpIP)
|
|
|
|
// 将字符串时间转换为 Unix 时间戳
|
|
if !isLive {
|
|
// 直接使用字符串格式的日期时间转换为秒级时间戳,不考虑时区问题
|
|
sdpInfo = append(sdpInfo, fmt.Sprintf("t=%d %d", d.start, d.end))
|
|
} else {
|
|
sdpInfo = append(sdpInfo, "t=0 0")
|
|
}
|
|
|
|
sdpInfo = append(sdpInfo, fmt.Sprintf("m=video %d TCP/RTP/AVP 96", d.MediaPort))
|
|
sdpInfo = append(sdpInfo, "a=recvonly")
|
|
sdpInfo = append(sdpInfo, "a=rtpmap:96 PS/90000")
|
|
//sdpInfo = append(sdpInfo, "a=rtpmap:98 H264/90000")
|
|
//sdpInfo = append(sdpInfo, "a=rtpmap:97 MPEG4/90000")
|
|
sdpInfo = append(sdpInfo, "a=setup:passive")
|
|
sdpInfo = append(sdpInfo, "a=connection:new")
|
|
|
|
if d.SSRC == 0 {
|
|
d.SSRC = uint32(ssrcValue)
|
|
}
|
|
|
|
// 将SSRC转换为字符串格式
|
|
ssrcStr := strconv.FormatUint(uint64(d.SSRC), 10)
|
|
sdpInfo = append(sdpInfo, fmt.Sprintf("y=%s", ssrcStr))
|
|
|
|
// 创建INVITE请求
|
|
request := sip.NewRequest(sip.INVITE, sip.Uri{User: channelId, Host: device.IP})
|
|
// 使用字符串格式的SSRC
|
|
subject := fmt.Sprintf("%s:%s,%s:0", channelId, ssrcStr, deviceId)
|
|
|
|
// 创建自定义头部
|
|
contentTypeHeader := sip.ContentTypeHeader("APPLICATION/SDP")
|
|
subjectHeader := sip.NewHeader("Subject", subject)
|
|
|
|
// 设置请求体
|
|
request.SetBody([]byte(strings.Join(sdpInfo, "\r\n") + "\r\n"))
|
|
|
|
recipient := device.Recipient
|
|
recipient.User = channelId
|
|
|
|
viaHeader := sip.ViaHeader{
|
|
ProtocolName: "SIP",
|
|
ProtocolVersion: "2.0",
|
|
Transport: "UDP",
|
|
Host: device.LocalIP,
|
|
Port: device.LocalPort,
|
|
Params: sip.HeaderParams(sip.NewParams()),
|
|
}
|
|
viaHeader.Params.Add("branch", sip.GenerateBranchN(16)).Add("rport", "")
|
|
fromHDR := sip.FromHeader{
|
|
Address: sip.Uri{
|
|
User: d.gb.Serial,
|
|
Host: d.gb.Realm,
|
|
},
|
|
Params: sip.NewParams(),
|
|
}
|
|
fromHDR.Params.Add("tag", sip.GenerateTagN(16))
|
|
// 创建会话 - 使用device的dialogClient创建
|
|
d.session, err = device.dialogClient.Invite(d.gb, recipient, request.Body(), &fromHDR, &viaHeader, &device.contactHDR, subjectHeader, &contentTypeHeader)
|
|
return
|
|
}
|
|
|
|
// Run 运行会话
|
|
func (d *ForwardDialog) Run() (err error) {
|
|
d.channel.Info("before WaitAnswer")
|
|
err = d.session.WaitAnswer(d.gb, sipgo.AnswerOptions{})
|
|
d.channel.Info("after WaitAnswer")
|
|
if err != nil {
|
|
return
|
|
}
|
|
inviteResponseBody := string(d.session.InviteResponse.Body())
|
|
d.channel.Info("inviteResponse", "body", inviteResponseBody)
|
|
ds := strings.Split(inviteResponseBody, "\r\n")
|
|
for _, l := range ds {
|
|
if ls := strings.Split(l, "="); len(ls) > 1 {
|
|
if ls[0] == "y" && len(ls[1]) > 0 {
|
|
if _ssrc, err := strconv.ParseInt(ls[1], 10, 0); err == nil {
|
|
d.SSRC = uint32(_ssrc)
|
|
} else {
|
|
d.gb.Error("read invite response y ", "err", err)
|
|
}
|
|
}
|
|
if ls[0] == "m" && len(ls[1]) > 0 {
|
|
netinfo := strings.Split(ls[1], " ")
|
|
if strings.ToUpper(netinfo[2]) == "TCP/RTP/AVP" {
|
|
d.gb.Debug("device support tcp")
|
|
} else {
|
|
d.gb.Debug("device using udp")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err = d.session.Ack(d.gb)
|
|
|
|
// 创建并初始化RTPForwarder
|
|
d.forwarder = gb28181.NewRTPForwarder()
|
|
d.forwarder.TCP = d.TCP
|
|
d.forwarder.TCPActive = d.TCPActive
|
|
|
|
// 设置监听地址和端口
|
|
d.forwarder.ListenAddr = fmt.Sprintf(":%d", d.MediaPort)
|
|
d.forwarder.ListenPort = d.MediaPort
|
|
|
|
// 设置转发目标
|
|
if d.platformIP != "" && d.platformPort > 0 {
|
|
err = d.forwarder.SetTarget(d.platformIP, d.platformPort)
|
|
if err != nil {
|
|
d.Error("set target error", "err", err)
|
|
return err
|
|
}
|
|
} else {
|
|
d.Error("no target set, will only receive but not forward")
|
|
return
|
|
}
|
|
|
|
// 设置目标SSRC
|
|
if d.platformSSRC != "" {
|
|
d.forwarder.TargetSSRC = d.platformSSRC
|
|
d.Info("set target ssrc", "ssrc", d.platformSSRC)
|
|
}
|
|
|
|
// 将forwarder添加到任务中
|
|
d.AddTask(d.forwarder)
|
|
|
|
d.Info("forwarder started successfully",
|
|
"TCP", d.forwarder.TCP,
|
|
"TCPActive", d.forwarder.TCPActive,
|
|
"listen", d.forwarder.ListenAddr,
|
|
"target", fmt.Sprintf("%s:%d", d.platformIP, d.platformPort),
|
|
"ssrc", d.platformSSRC)
|
|
|
|
// 使用goroutine启动Demux,避免阻塞
|
|
d.forwarder.Demux()
|
|
|
|
return
|
|
}
|
|
|
|
// Dispose 释放会话资源
|
|
func (d *ForwardDialog) Dispose() {
|
|
if d.session != nil {
|
|
err := d.session.Bye(d)
|
|
if err != nil {
|
|
d.Error("forwarddialog bye bye err", err)
|
|
}
|
|
err = d.session.Close()
|
|
if err != nil {
|
|
d.Error("forwarddialog close session err", err)
|
|
}
|
|
}
|
|
}
|