pion和freeswitch通讯
用golang编写webrtc客户端负责和freeswitch通讯,支持呼入呼出
Softphone代码如下
支持注册和取消注册
func (softphone *Softphone) register() {
url := url.URL{Scheme: strings.ToLower(softphone.sipInfo.Transport), Host: softphone.sipInfo.OutboundProxy, Path: "/sip/"}
dialer := websocket.DefaultDialer
dialer.Subprotocols = []string{"sip"}
dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint
header := http.Header{}
header.Set("origin", "http://192.168.1.1")
conn, _, err := dialer.Dial(url.String(), header)
if err != nil {
log.Fatal(err)
}
softphone.wsConn = conn
go func() {
for {
messageType, bytes, err := conn.ReadMessage()
if err != nil {
log.Fatal(err)
}
switch messageType {
case websocket.TextMessage:
fmt.Println("TextMessage")
case websocket.BinaryMessage: //二进制数据
fmt.Println("BinaryMessage")
case websocket.CloseMessage: //关闭
fmt.Println("CloseMessage")
break
case websocket.PingMessage: //Ping
fmt.Println("PingMessage")
break
case websocket.PongMessage: //Pong
fmt.Println("PongMessage")
break
}
message := string(bytes)
log.Print("↓↓↓\n", message)
for _, ml := range softphone.messageListeners {
go ml(message)
}
}
}()
sipMessage := SIPMessage{}
sipMessage.method = "REGISTER"
sipMessage.address = softphone.sipInfo.Domain
sipMessage.headers = make(map[string]string)
sipMessage.headers["Contact"] = fmt.Sprintf("<sip:%s;transport=ws>;expires=200", softphone.FakeEmail)
sipMessage.headers["Via"] = fmt.Sprintf("SIP/2.0/WS %s;branch=%s", softphone.fakeDomain, branch())
sipMessage.headers["From"] = fmt.Sprintf("<sip:%s@%s>;tag=%s", softphone.sipInfo.Username, softphone.sipInfo.Domain, softphone.fromTag)
sipMessage.headers["To"] = fmt.Sprintf("<sip:%s@%s>", softphone.sipInfo.Username, softphone.sipInfo.Domain)
sipMessage.headers["Organization"] = "ACE MEDIAS TOOLS"
sipMessage.headers["Supported"] = "path,ice"
sipMessage.addCseq(softphone).addCallID(*softphone).addUserAgent()
//registered, registeredFunc := context.WithCancel(context.Background())
softphone.request(sipMessage, func(message string) bool {
authenticateHeader := SIPMessage{}.FromString(message).headers["WWW-Authenticate"]
regex := regexp.MustCompile(`, nonce="(.+?)"`)
nonce := regex.FindStringSubmatch(authenticateHeader)[1]
sipMessage.addAuthorization(*softphone, nonce, "REGISTER").addCseq(softphone).newViaBranch()
softphone.request(sipMessage, func(msg string) bool {
if strings.HasPrefix(msg, "SIP/2.0 200 OK") {
registerSuccessHeader := SIPMessage{}.FromString(msg).headers["Contact"]
softphone.Contact = registerSuccessHeader
softphone.Registerd <- true
}
//registeredFunc()
return false
})
return true
})
//<-registered.Done()
}
func (softphone *Softphone) Unregister() {
sipMessage := SIPMessage{}
sipMessage.method = "REGISTER"
sipMessage.address = softphone.sipInfo.Domain
sipMessage.headers = make(map[string]string)
sipMessage.headers["Contact"] = fmt.Sprintf("<sip:%s;transport=ws>;expires=0", softphone.FakeEmail)
sipMessage.headers["Via"] = fmt.Sprintf("SIP/2.0/WS %s;branch=%s", softphone.fakeDomain, branch())
sipMessage.headers["From"] = fmt.Sprintf("<sip:%s@%s>;tag=%s", softphone.sipInfo.Username, softphone.sipInfo.Domain, softphone.fromTag)
sipMessage.headers["To"] = fmt.Sprintf("<sip:%s@%s>", softphone.sipInfo.Username, softphone.sipInfo.Domain)
sipMessage.headers["Organization"] = "ACE MEDIAS TOOLS"
sipMessage.headers["Supported"] = "path,ice"
sipMessage.addCseq(softphone).addCallID(*softphone).addUserAgent()
registered, registeredFunc := context.WithCancel(context.Background())
softphone.request(sipMessage, func(message string) bool {
authenticateHeader := SIPMessage{}.FromString(message).headers["WWW-Authenticate"]
regex := regexp.MustCompile(`, nonce="(.+?)"`)
nonce := regex.FindStringSubmatch(authenticateHeader)[1]
sipMessage.addAuthorization(*softphone, nonce, "REGISTER").addCseq(softphone).newViaBranch()
softphone.request(sipMessage, func(msg string) bool {
if strings.HasPrefix(msg, "SIP/2.0 200 OK") {
registeredFunc()
}
return true
})
return true
})
<-registered.Done()
}
外拨和应答
func (softphone *Softphone) Invite(extension, offer string) {
sipMessage := SIPMessage{headers: map[string]string{}}
sipMessage.method = "INVITE"
sipMessage.address = softphone.sipInfo.Domain
sipMessage.headers["Contact"] = fmt.Sprintf("<sip:%s;transport=ws>;expires=200", softphone.FakeEmail)
sipMessage.headers["To"] = fmt.Sprintf("<sip:%s@%s>", extension, softphone.sipInfo.Domain)
sipMessage.headers["Via"] = fmt.Sprintf("SIP/2.0/WS %s;branch=%s", softphone.fakeDomain, branch())
sipMessage.headers["From"] = fmt.Sprintf("<sip:%s@%s>;tag=%s", softphone.sipInfo.Username, softphone.sipInfo.Domain, softphone.fromTag)
sipMessage.headers["Supported"] = "replaces, outbound,ice"
sipMessage.addCseq(softphone).addCallID(*softphone).addUserAgent()
sipMessage.headers["Content-Type"] = "application/sdp"
sipMessage.Body = offer
softphone.State = Dial
invited, invitedFunc := context.WithCancel(context.Background())
softphone.request(sipMessage, func(message string) bool {
//authenticateHeader := SIPMessage{}.FromString(message).headers["Proxy-Authenticate"]
//regex := regexp.MustCompile(`, nonce="(.+?)"`)
//nonce := regex.FindStringSubmatch(authenticateHeader)[1]
//sipMessage.addProxyAuthorization(*softphone, nonce, extension, "INVITE").addCseq(softphone).newViaBranch()
//softphone.request(sipMessage, func(msg string) bool {
// return false
//})
if strings.HasPrefix(message, "SIP/2.0 100 Trying") {
return false
} else if strings.HasPrefix(message, "SIP/2.0 200 OK") {
//回复ACK
parsed := SIPMessage{}.FromString(message)
vias := strings.Split(parsed.headers["Via"], ";")
contacts := strings.Split(parsed.headers["Contact"], " ")
sipMessage := SIPMessage{headers: map[string]string{}}
sipMessage.method = "ACK"
sipMessage.address = strings.Replace(strings.Replace(contacts[0], "<", "", -1), ">", "", -1)
sipMessage.headers["To"] = parsed.headers["To"]
sipMessage.headers["Via"] = fmt.Sprintf("%s;branch=%s;%s;%s", vias[0], branch(), vias[2], vias[3])
sipMessage.headers["From"] = parsed.headers["From"]
sipMessage.headers["CSeq"] = strings.Replace(parsed.headers["CSeq"], "INVITE", "ACK", -1)
sipMessage.headers["Call-ID"] = parsed.headers["Call-ID"]
softphone.State = Busy
softphone.request(sipMessage, func(msg string) bool {
invitedFunc()
return false
})
return true
} else {
return true
}
})
<-invited.Done()
}
// OnOK adds a handler that responds to any outbound ok events.
func (softphone *Softphone) OnOK(hdlr func(string)) {
softphone.addMessageListener(func(message string) {
if strings.HasPrefix(message, "SIP/2.0 200 OK") {
parsed := SIPMessage{}.FromString(message)
hdlr(parsed.Body)
}
})
}
呼入
// CloseToInvite removes the previously set invite listener.
func (softphone *Softphone) CloseToInvite() {
softphone.removeMessageListener(softphone.inviteKey)
}
//inbound call
func (softphone *Softphone) OnInvite(hdlr func(string)) {
softphone.addMessageListener(func(message string) {
if strings.HasPrefix(message, "INVITE sip:") {
parsed := SIPMessage{}.FromString(message)
softphone.InboundInviteMessage = message
hdlr(parsed.Body)
}
})
}
//answer inbound call
func (softphone *Softphone) InOK(answer string) {
sipMessage := SIPMessage{}.FromString(softphone.InboundInviteMessage)
dict0 := map[string]string{
//"Contact": fmt.Sprintf(`<sip:%s;transport=ws>`, softphone.fakeDomain),
"Contact": softphone.Contact,
"Supported": "outbound",
}
response_180Msg := sipMessage.Response(*softphone, 180, dict0, "")
softphone.response(response_180Msg)
time.Sleep(2000)
//sipMessage.headers["Supported"] = "replaces, 100rel, timer, norefersub"
//sipMessage.headers["Content-Type"] = "application/sdp"
//sipMessage.addCseq(softphone).addCallID(*softphone).addUserAgent()
//sipMessage.Body = fmt.Sprintf(`<Msg><Hdr SID="%s" Req="%s" From="%s" To="%s" Cmd="17"/><Bdy Cln="%s"/></Msg>`, msg.Hdr.SID, msg.Hdr.Req, msg.Hdr.To, msg.Hdr.From, softphone.sipInfo.AuthorizationID)
dict1 := map[string]string{
//"Contact": fmt.Sprintf(`<sip:%s;transport=ws>`, softphone.fakeDomain),
"Contact": softphone.Contact,
"Supported": "replaces, outbound,ice",
"Content-Type": "application/sdp",
}
response_200Msg := sipMessage.Response(*softphone, 200, dict1, answer)
softphone.response(response_200Msg)
}
main
func main() {
flag.Parse()
if *host == "" || *port == "" || *password == "" {
panic("-host -port and -password are required")
}
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
//设置要接收的信号
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
conn := softphone.NewSoftPhone(softphone.SIPInfoResponse{
Username: *username,
AuthorizationID: *username,
Password: *password,
Domain: *host,
Transport: "ws",
OutboundProxy: *host + ":" + *port,
})
register := <-conn.Registerd
if !register {
panic("not register")
}
conn.OnBye()
conn.OnInvite(func(okBody string) {
if conn.State != softphone.Idle {
return
}
conn.State = softphone.Ring
// Prepare the configuration
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:172.22.13.231:16384"},
},
},
}
pc, err := webrtc.NewPeerConnection(config)
if err != nil {
panic(err)
}
// If PeerConnection is closed remove it from global list
pc.OnConnectionStateChange(func(p webrtc.PeerConnectionState) {
switch p {
case webrtc.PeerConnectionStateFailed:
if err := pc.Close(); err != nil {
log.Print(err)
}
conn.SetPeerConnection(nil)
case webrtc.PeerConnectionStateClosed:
conn.SetPeerConnection(nil)
case webrtc.PeerConnectionStateConnected:
conn.SetPeerConnection(pc)
}
})
pc.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
if connectionState == webrtc.ICEConnectionStateDisconnected {
conn.SetPeerConnection(nil)
}
})
if _, err = pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil {
panic(err)
}
pc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
//codec := track.Codec()
//fmt.Println("Got Opus track, saving to disk as inbound.ogg", codec.MimeType)
//if codec.MimeType == "audio/opus" {
fmt.Println("Got Opus track, saving to disk as inbound.ogg")
//i, oggNewErr := oggwriter.New("inbound.ogg", codec.ClockRate, codec.Channels)
//i, oggNewErr := oggwriter.New("inbound.ogg", 48000, 1)
//if oggNewErr != nil {
// panic(oggNewErr)
//}
//saveToDisk(i, track)
//}
})
if !strings.Contains(okBody, "a=sendonly") &&
!strings.Contains(okBody, "a=recvonly") &&
!strings.Contains(okBody, "a=sendrecv") &&
!strings.Contains(okBody, "a=inactive") {
okBody += "a=sendrecv\r\n"
}
if !strings.Contains(okBody, "a=mid:0") {
okBody += "a=mid:0\r\n"
}
if err := pc.SetRemoteDescription(webrtc.SessionDescription{Type: webrtc.SDPTypeOffer, SDP: okBody}); err != nil {
panic(err)
}
// Create an answer
answer, err := pc.CreateAnswer(nil)
if err != nil {
panic(err)
}
if err := pc.SetLocalDescription(answer); err != nil {
panic(err)
}
conn.InOK(rewriteSDP(answer.SDP))
})
go func() {
<-sigs
conn.Unregister()
done <- true
}()
<-done
fmt.Println("进程被终止")
}