pion是一个纯用golang写的开源webrtc项目,目前github上大部分webrtc开源都是使用c++写的。本人认为pion对于一个webrtc入门者来说golang的语法相对简单,你这样可以更好的关注webrtc协议。如果业务场景不是特别高,业务上只做sfu转发服务器的话,可以尝试用golang来做。
读这篇文章你需要稍微了解webrtc的协议。
pion也有提供不少的例子,我这边写了一个我自认为最简单,只能接受一个流的例子。这个例子平铺直叙,走了一个完整的webrtc流程,需要配合前端https://jsfiddle.net/1jc4go7v/当然需要注意的是前端只有publish能用。
整体下来就是:
创建peerconnection,设置stun服务器设置对端的sdp创建自己协商的answer,设置answer等待candidate收集完成,输出序列化后的sdp信息这边就是一个最简单的接受流的例子。
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/pion/webrtc/v3"
)
func main() {
//创建pc, 并且指定stun服务器
pc, err := webrtc.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
})
if err != nil {
panic(err.Error())
}
sdp := webrtc.SessionDescription{}
//todo:这边需要你自己设置对端的sdp信息
sdpStr := "eyJ0eXBlIjoib2ZmZXIiLCJzZHAiOiJ2PTBcclxubz0tIDE2OTQ4Nzk1NDc5OTk0ODE3MDYgMiBJTiBJUDQgMTI3LjAuMC4xXHJcbnM9LVxyXG50PTAgMFxyXG5hPWdyb3VwOkJVTkRMRSAwXHJcbmE9ZXh0bWFwLWFsbG93LW1peGVkXHJcbmE9bXNpZC1zZW1hbnRpYzogV01TIE1PQ0xmd21HT1hwRG41dlB5R3lyZU5nclJ0Y2xQR2xCVmdjSlxyXG5tPXZpZGVvIDUzMzUxIFVEUC9UTFMvUlRQL1NBVlBGIDk2IDk3IDk4IDk5IDEwMCAxMDEgMTAyIDEyMSAxMjcgMTIwIDEyNSAxMDcgMTA4IDEwOSAzNSAzNiAxMjQgMTE5IDEyMyAxMTggMTE0IDExNSAxMTZcclxuYz1JTiBJUDQgNDcuOTEuMjE2LjE5XHJcbmE9cnRjcDo5IElOIElQNCAwLjAuMC4wXHJcbmE9Y2FuZGlkYXRlOjIwOTQwMjg4ODIgMSB1ZHAgMjEyMjI2MDIyMyAxMC4xMDYuMjEyLjc5IDUzMzUxIHR5cCBob3N0IGdlbmVyYXRpb24gMCBuZXR3b3JrLWlkIDEgbmV0d29yay1jb3N0IDEwXHJcbmE9Y2FuZGlkYXRlOjg0NDM0MjQzNCAxIHRjcCAxNTE4MjgwNDQ3IDEwLjEwNi4yMTIuNzkgOSB0eXAgaG9zdCB0Y3B0eXBlIGFjdGl2ZSBnZW5lcmF0aW9uIDAgbmV0d29yay1pZCAxIG5ldHdvcmstY29zdCAxMFxyXG5hPWNhbmRpZGF0ZTo0MjU0NjIxNDE0IDEgdWRwIDE2ODYwNTI2MDcgNDcuOTEuMjE2LjE5IDUzMzUxIHR5cCBzcmZseCByYWRkciAxMC4xMDYuMjEyLjc5IHJwb3J0IDUzMzUxIGdlbmVyYXRpb24gMCBuZXR3b3JrLWlkIDEgbmV0d29yay1jb3N0IDEwXHJcbmE9aWNlLXVmcmFnOjAxTDJcclxuYT1pY2UtcHdkOjlVOEs4bVp0R0hka0UzK2tHcVl6ODZ4dVxyXG5hPWljZS1vcHRpb25zOnRyaWNrbGVcclxuYT1maW5nZXJwcmludDpzaGEtMjU2IEM1OjM0OjQ4OjlEOjU3OjlCOjhDOkU5OjhCOjI0OjMxOkEwOkIzOjFCOkI1OjJEOjMyOkY0OkJFOjE2OjgxOjhCOjJDOkIyOkFBOjRDOjI5OjI3OkIwOjQ4OjNCOjgxXHJcbmE9c2V0dXA6YWN0cGFzc1xyXG5hPW1pZDowXHJcbmE9ZXh0bWFwOjEgdXJuOmlldGY6cGFyYW1zOnJ0cC1oZHJleHQ6dG9mZnNldFxyXG5hPWV4dG1hcDoyIGh0dHA6Ly93d3cud2VicnRjLm9yZy9leHBlcmltZW50cy9ydHAtaGRyZXh0L2Ficy1zZW5kLXRpbWVcclxuYT1leHRtYXA6MyB1cm46M2dwcDp2aWRlby1vcmllbnRhdGlvblxyXG5hPWV4dG1hcDo0IGh0dHA6Ly93d3cuaWV0Zi5vcmcvaWQvZHJhZnQtaG9sbWVyLXJtY2F0LXRyYW5zcG9ydC13aWRlLWNjLWV4dGVuc2lvbnMtMDFcclxuYT1leHRtYXA6NSBodHRwOi8vd3d3LndlYnJ0Yy5vcmcvZXhwZXJpbWVudHMvcnRwLWhkcmV4dC9wbGF5b3V0LWRlbGF5XHJcbmE9ZXh0bWFwOjYgaHR0cDovL3d3dy53ZWJydGMub3JnL2V4cGVyaW1lbnRzL3J0cC1oZHJleHQvdmlkZW8tY29udGVudC10eXBlXHJcbmE9ZXh0bWFwOjcgaHR0cDovL3d3dy53ZWJydGMub3JnL2V4cGVyaW1lbnRzL3J0cC1oZHJleHQvdmlkZW8tdGltaW5nXHJcbmE9ZXh0bWFwOjggaHR0cDovL3d3dy53ZWJydGMub3JnL2V4cGVyaW1lbnRzL3J0cC1oZHJleHQvY29sb3Itc3BhY2VcclxuYT1leHRtYXA6OSB1cm46aWV0ZjpwYXJhbXM6cnRwLWhkcmV4dDpzZGVzOm1pZFxyXG5hPWV4dG1hcDoxMCB1cm46aWV0ZjpwYXJhbXM6cnRwLWhkcmV4dDpzZGVzOnJ0cC1zdHJlYW0taWRcclxuYT1leHRtYXA6MTEgdXJuOmlldGY6cGFyYW1zOnJ0cC1oZHJleHQ6c2RlczpyZXBhaXJlZC1ydHAtc3RyZWFtLWlkXHJcbmE9c2VuZHJlY3ZcclxuYT1tc2lkOk1PQ0xmd21HT1hwRG41dlB5R3lyZU5nclJ0Y2xQR2xCVmdjSiAzMTU2MTk3Ny04MzI3LTQ4MzUtYmNkYi05NTk0MGY0YTE5NWFcclxuYT1ydGNwLW11eFxyXG5hPXJ0Y3AtcnNpemVcclxuYT1ydHBtYXA6OTYgVlA4LzkwMDAwXHJcbmE9cnRjcC1mYjo5NiBnb29nLXJlbWJcclxuYT1ydGNwLWZiOjk2IHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6OTYgY2NtIGZpclxyXG5hPXJ0Y3AtZmI6OTYgbmFja1xyXG5hPXJ0Y3AtZmI6OTYgbmFjayBwbGlcclxuYT1ydHBtYXA6OTcgcnR4LzkwMDAwXHJcbmE9Zm10cDo5NyBhcHQ9OTZcclxuYT1ydHBtYXA6OTggVlA5LzkwMDAwXHJcbmE9cnRjcC1mYjo5OCBnb29nLXJlbWJcclxuYT1ydGNwLWZiOjk4IHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6OTggY2NtIGZpclxyXG5hPXJ0Y3AtZmI6OTggbmFja1xyXG5hPXJ0Y3AtZmI6OTggbmFjayBwbGlcclxuYT1mbXRwOjk4IHByb2ZpbGUtaWQ9MFxyXG5hPXJ0cG1hcDo5OSBydHgvOTAwMDBcclxuYT1mbXRwOjk5IGFwdD05OFxyXG5hPXJ0cG1hcDoxMDAgVlA5LzkwMDAwXHJcbmE9cnRjcC1mYjoxMDAgZ29vZy1yZW1iXHJcbmE9cnRjcC1mYjoxMDAgdHJhbnNwb3J0LWNjXHJcbmE9cnRjcC1mYjoxMDAgY2NtIGZpclxyXG5hPXJ0Y3AtZmI6MTAwIG5hY2tcclxuYT1ydGNwLWZiOjEwMCBuYWNrIHBsaVxyXG5hPWZtdHA6MTAwIHByb2ZpbGUtaWQ9MlxyXG5hPXJ0cG1hcDoxMDEgcnR4LzkwMDAwXHJcbmE9Zm10cDoxMDEgYXB0PTEwMFxyXG5hPXJ0cG1hcDoxMDIgSDI2NC85MDAwMFxyXG5hPXJ0Y3AtZmI6MTAyIGdvb2ctcmVtYlxyXG5hPXJ0Y3AtZmI6MTAyIHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6MTAyIGNjbSBmaXJcclxuYT1ydGNwLWZiOjEwMiBuYWNrXHJcbmE9cnRjcC1mYjoxMDIgbmFjayBwbGlcclxuYT1mbXRwOjEwMiBsZXZlbC1hc3ltbWV0cnktYWxsb3dlZD0xO3BhY2tldGl6YXRpb24tbW9kZT0xO3Byb2ZpbGUtbGV2ZWwtaWQ9NDIwMDFmXHJcbmE9cnRwbWFwOjEyMSBydHgvOTAwMDBcclxuYT1mbXRwOjEyMSBhcHQ9MTAyXHJcbmE9cnRwbWFwOjEyNyBIMjY0LzkwMDAwXHJcbmE9cnRjcC1mYjoxMjcgZ29vZy1yZW1iXHJcbmE9cnRjcC1mYjoxMjcgdHJhbnNwb3J0LWNjXHJcbmE9cnRjcC1mYjoxMjcgY2NtIGZpclxyXG5hPXJ0Y3AtZmI6MTI3IG5hY2tcclxuYT1ydGNwLWZiOjEyNyBuYWNrIHBsaVxyXG5hPWZtdHA6MTI3IGxldmVsLWFzeW1tZXRyeS1hbGxvd2VkPTE7cGFja2V0aXphdGlvbi1tb2RlPTA7cHJvZmlsZS1sZXZlbC1pZD00MjAwMWZcclxuYT1ydHBtYXA6MTIwIHJ0eC85MDAwMFxyXG5hPWZtdHA6MTIwIGFwdD0xMjdcclxuYT1ydHBtYXA6MTI1IEgyNjQvOTAwMDBcclxuYT1ydGNwLWZiOjEyNSBnb29nLXJlbWJcclxuYT1ydGNwLWZiOjEyNSB0cmFuc3BvcnQtY2NcclxuYT1ydGNwLWZiOjEyNSBjY20gZmlyXHJcbmE9cnRjcC1mYjoxMjUgbmFja1xyXG5hPXJ0Y3AtZmI6MTI1IG5hY2sgcGxpXHJcbmE9Zm10cDoxMjUgbGV2ZWwtYXN5bW1ldHJ5LWFsbG93ZWQ9MTtwYWNrZXRpemF0aW9uLW1vZGU9MTtwcm9maWxlLWxldmVsLWlkPTQyZTAxZlxyXG5hPXJ0cG1hcDoxMDcgcnR4LzkwMDAwXHJcbmE9Zm10cDoxMDcgYXB0PTEyNVxyXG5hPXJ0cG1hcDoxMDggSDI2NC85MDAwMFxyXG5hPXJ0Y3AtZmI6MTA4IGdvb2ctcmVtYlxyXG5hPXJ0Y3AtZmI6MTA4IHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6MTA4IGNjbSBmaXJcclxuYT1ydGNwLWZiOjEwOCBuYWNrXHJcbmE9cnRjcC1mYjoxMDggbmFjayBwbGlcclxuYT1mbXRwOjEwOCBsZXZlbC1hc3ltbWV0cnktYWxsb3dlZD0xO3BhY2tldGl6YXRpb24tbW9kZT0wO3Byb2ZpbGUtbGV2ZWwtaWQ9NDJlMDFmXHJcbmE9cnRwbWFwOjEwOSBydHgvOTAwMDBcclxuYT1mbXRwOjEwOSBhcHQ9MTA4XHJcbmE9cnRwbWFwOjM1IEFWMVgvOTAwMDBcclxuYT1ydGNwLWZiOjM1IGdvb2ctcmVtYlxyXG5hPXJ0Y3AtZmI6MzUgdHJhbnNwb3J0LWNjXHJcbmE9cnRjcC1mYjozNSBjY20gZmlyXHJcbmE9cnRjcC1mYjozNSBuYWNrXHJcbmE9cnRjcC1mYjozNSBuYWNrIHBsaVxyXG5hPXJ0cG1hcDozNiBydHgvOTAwMDBcclxuYT1mbXRwOjM2IGFwdD0zNVxyXG5hPXJ0cG1hcDoxMjQgSDI2NC85MDAwMFxyXG5hPXJ0Y3AtZmI6MTI0IGdvb2ctcmVtYlxyXG5hPXJ0Y3AtZmI6MTI0IHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6MTI0IGNjbSBmaXJcclxuYT1ydGNwLWZiOjEyNCBuYWNrXHJcbmE9cnRjcC1mYjoxMjQgbmFjayBwbGlcclxuYT1mbXRwOjEyNCBsZXZlbC1hc3ltbWV0cnktYWxsb3dlZD0xO3BhY2tldGl6YXRpb24tbW9kZT0xO3Byb2ZpbGUtbGV2ZWwtaWQ9NGQwMDMyXHJcbmE9cnRwbWFwOjExOSBydHgvOTAwMDBcclxuYT1mbXRwOjExOSBhcHQ9MTI0XHJcbmE9cnRwbWFwOjEyMyBIMjY0LzkwMDAwXHJcbmE9cnRjcC1mYjoxMjMgZ29vZy1yZW1iXHJcbmE9cnRjcC1mYjoxMjMgdHJhbnNwb3J0LWNjXHJcbmE9cnRjcC1mYjoxMjMgY2NtIGZpclxyXG5hPXJ0Y3AtZmI6MTIzIG5hY2tcclxuYT1ydGNwLWZiOjEyMyBuYWNrIHBsaVxyXG5hPWZtdHA6MTIzIGxldmVsLWFzeW1tZXRyeS1hbGxvd2VkPTE7cGFja2V0aXphdGlvbi1tb2RlPTE7cHJvZmlsZS1sZXZlbC1pZD02NDAwMzJcclxuYT1ydHBtYXA6MTE4IHJ0eC85MDAwMFxyXG5hPWZtdHA6MTE4IGFwdD0xMjNcclxuYT1ydHBtYXA6MTE0IHJlZC85MDAwMFxyXG5hPXJ0cG1hcDoxMTUgcnR4LzkwMDAwXHJcbmE9Zm10cDoxMTUgYXB0PTExNFxyXG5hPXJ0cG1hcDoxMTYgdWxwZmVjLzkwMDAwXHJcbmE9c3NyYy1ncm91cDpGSUQgMjY2MjI0MzgzNiAxMDM2NTM2NTlcclxuYT1zc3JjOjI2NjIyNDM4MzYgY25hbWU6STk3MEs2VDVSWGZHRkRxaFxyXG5hPXNzcmM6MjY2MjI0MzgzNiBtc2lkOk1PQ0xmd21HT1hwRG41dlB5R3lyZU5nclJ0Y2xQR2xCVmdjSiAzMTU2MTk3Ny04MzI3LTQ4MzUtYmNkYi05NTk0MGY0YTE5NWFcclxuYT1zc3JjOjI2NjIyNDM4MzYgbXNsYWJlbDpNT0NMZndtR09YcERuNXZQeUd5cmVOZ3JSdGNsUEdsQlZnY0pcclxuYT1zc3JjOjI2NjIyNDM4MzYgbGFiZWw6MzE1NjE5NzctODMyNy00ODM1LWJjZGItOTU5NDBmNGExOTVhXHJcbmE9c3NyYzoxMDM2NTM2NTkgY25hbWU6STk3MEs2VDVSWGZHRkRxaFxyXG5hPXNzcmM6MTAzNjUzNjU5IG1zaWQ6TU9DTGZ3bUdPWHBEbjV2UHlHeXJlTmdyUnRjbFBHbEJWZ2NKIDMxNTYxOTc3LTgzMjctNDgzNS1iY2RiLTk1OTQwZjRhMTk1YVxyXG5hPXNzcmM6MTAzNjUzNjU5IG1zbGFiZWw6TU9DTGZ3bUdPWHBEbjV2UHlHeXJlTmdyUnRjbFBHbEJWZ2NKXHJcbmE9c3NyYzoxMDM2NTM2NTkgbGFiZWw6MzE1NjE5NzctODMyNy00ODM1LWJjZGItOTU5NDBmNGExOTVhXHJcbiJ9"
//前端页面会对sdp进行base64的encode
b, err := base64.StdEncoding.DecodeString(sdpStr)
if err != nil {
panic(err)
}
//反序列化为sdp
if err = json.Unmarshal(b, &sdp); err != nil {
panic(err.Error())
}
//设置远端的sdp
if err = pc.SetRemoteDescription(sdp); err != nil {
panic(err.Error())
}
//创建协商结果
answer, err := pc.CreateAnswer(nil)
if err != nil {
panic(err.Error())
}
//设置自己的协商结果
if err = pc.SetLocalDescription(answer); err != nil {
panic(err)
}
//等待ice结束
gatherCmp := webrtc.GatheringCompletePromise(pc)
<-gatherCmp
//将协商并且收集完candidate的answer,输出到控制台
answerBytes, err := json.Marshal(*pc.LocalDescription())
if err != nil {
panic(err)
}
/*
//这段代码可以省略,也可以放开。他可以清楚的让你知道你是否推流成功
pc.OnTrack(func(remote *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
fmt.Println("one track = ", remote.PayloadType())
rtpBuf := make([]byte, 1400)
for {
n, as, err := remote.Read(rtpBuf)
fmt.Println("n = ", n, " as = ", as, " err = ", err)
}
})
*/
fmt.Println(base64.StdEncoding.EncodeToString(answerBytes))
select {
}
}
流程
创建peerconnection
一开始我们需要创建一个peerconnection,这个会接受配置信息如下:
type Configuration struct {
//stun或者turn服务器信息,为空只会有host
ICEServers []ICEServer `json:"iceServers,omitempty"`
//这个会规定
//ICETransportPolicyAll 会收集所有的candidate,默认
//ICETransportPolicyRelay 只会收集relay的candidate
ICETransportPolicy ICETransportPolicy `json:"iceTransportPolicy,omitempty"`
//BundlePolicyBalanced 相当于“balanced“,这种情况下音频和视频只会建立两个传输的通道进行传输。如果对端不支持这种方式,那么会将各个轨道的音频分开。默认
//BundlePolicyMaxCompat 相当于“max-compat”,这种情况下会将所有的媒体轨道分开不同的通道做传输,音频可能会有多条通道传输,视频可能一条。如果对端不支持blanced会回退到这种模式。
//BundlePolicyMaxBundle 相当于“max-bundle”,这种情况下会将所有的媒体只放到一个通道传输。如果对端不支持单通道,可能会有丢失媒体轨道。
BundlePolicy BundlePolicy `json:"bundlePolicy,omitempty"`
//RTCPMuxPolicyNegotiate rtp和rtcp是否复用一个candidate,可以经过协商后得到。如果对端支持端口复用,那么就共用一个candidate。如果对端不支持一个candidate,那么就分开。默认。
//RTCPMuxPolicyRequire rtp和rtcp必须要复用一个candidate,否则会失败
RTCPMuxPolicy RTCPMuxPolicy `json:"rtcpMuxPolicy,omitempty"`
//对端的身份标示,默认为空。
PeerIdentity string `json:"peerIdentity,omitempty"`
//DTLS建立时的证书,如果没有填,会自己生成。
Certificates []Certificate `json:"certificates,omitempty"`
//当指定大小会提前去收集size个candidate供你使用,就不用等到setLocalDescription()再去收集。默认是0。
ICECandidatePoolSize uint8 `json:"iceCandidatePoolSize,omitempty"`
//sdp交互的格式,以下3种值。
//SDPSemanticsUnifiedPlan sdp的格式为unified plan
//SDPSemanticsPlanB sdp格式为plan b
//SDPSemanticsUnifiedPlanWithFallback 取决于offer的格式,如果unified plan会会unified plan,如果是plan b会回plan b
SDPSemantics SDPSemantics `json:"sdpSemantics,omitempty"`
}
webrtc.NewPeerConnection函数会去初始化一些配置,做nack的处理等。
设置对端的sdpsdp信息我们没有经过信令服务器做转发,做了一个最朴素的方法,直接写到代码里面,通过反序列化后直接SetRemoteDescription到pc中。pc会和默认支持的code做协商、设置remote Candidate、开始调用ICE的联通性检查等 *** 作。但是值得注意的是,真正开始连通性检查时需要双端的candidate都有。
创建、设置answer这个时候会根据协商的内容,创建不同的媒体流,并且在设置的时候会开始收集信息自己的candidate。
输出最终answer在SetLocalDescription的时候会开始收集,gatherCmp管道会在收集完成的时候收到内容。当收集完成我们就可以将本地的Candidate发送到远端。当然现在已经有ticker ice了,并不需要全部收集完成再发送,我们这边为了简单用的还是比较旧的方式。当我们收集完Candidate,这个时候就可以将本地的sdp经过序列化发送给远端了,剩下的就是连通性检查,然后推流了。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)