github.com/pion/webrtc/v4@v4.0.1/examples/rtp-forwarder/main.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 // rtp-forwarder shows how to forward your webcam/microphone via RTP using Pion WebRTC. 8 package main 9 10 import ( 11 "bufio" 12 "encoding/base64" 13 "encoding/json" 14 "errors" 15 "fmt" 16 "io" 17 "net" 18 "os" 19 "strings" 20 21 "github.com/pion/interceptor" 22 "github.com/pion/interceptor/pkg/intervalpli" 23 "github.com/pion/rtp" 24 "github.com/pion/webrtc/v4" 25 ) 26 27 type udpConn struct { 28 conn *net.UDPConn 29 port int 30 payloadType uint8 31 } 32 33 // nolint:gocognit 34 func main() { 35 // Everything below is the Pion WebRTC API! Thanks for using it ❤️. 36 37 // Create a MediaEngine object to configure the supported codec 38 m := &webrtc.MediaEngine{} 39 40 // Setup the codecs you want to use. 41 // We'll use a VP8 and Opus but you can also define your own 42 if err := m.RegisterCodec(webrtc.RTPCodecParameters{ 43 RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil}, 44 }, webrtc.RTPCodecTypeVideo); err != nil { 45 panic(err) 46 } 47 if err := m.RegisterCodec(webrtc.RTPCodecParameters{ 48 RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil}, 49 }, webrtc.RTPCodecTypeAudio); err != nil { 50 panic(err) 51 } 52 53 // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 54 // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 55 // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 56 // for each PeerConnection. 57 i := &interceptor.Registry{} 58 59 // Register a intervalpli factory 60 // This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender. 61 // This makes our video seekable and more error resilent, but at a cost of lower picture quality and higher bitrates 62 // A real world application should process incoming RTCP packets from viewers and forward them to senders 63 intervalPliFactory, err := intervalpli.NewReceiverInterceptor() 64 if err != nil { 65 panic(err) 66 } 67 i.Add(intervalPliFactory) 68 69 // Use the default set of Interceptors 70 if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil { 71 panic(err) 72 } 73 74 // Create the API object with the MediaEngine 75 api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)) 76 77 // Prepare the configuration 78 config := webrtc.Configuration{ 79 ICEServers: []webrtc.ICEServer{ 80 { 81 URLs: []string{"stun:stun.l.google.com:19302"}, 82 }, 83 }, 84 } 85 86 // Create a new RTCPeerConnection 87 peerConnection, err := api.NewPeerConnection(config) 88 if err != nil { 89 panic(err) 90 } 91 defer func() { 92 if cErr := peerConnection.Close(); cErr != nil { 93 fmt.Printf("cannot close peerConnection: %v\n", cErr) 94 } 95 }() 96 97 // Allow us to receive 1 audio track, and 1 video track 98 if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil { 99 panic(err) 100 } else if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil { 101 panic(err) 102 } 103 104 // Create a local addr 105 var laddr *net.UDPAddr 106 if laddr, err = net.ResolveUDPAddr("udp", "127.0.0.1:"); err != nil { 107 panic(err) 108 } 109 110 // Prepare udp conns 111 // Also update incoming packets with expected PayloadType, the browser may use 112 // a different value. We have to modify so our stream matches what rtp-forwarder.sdp expects 113 udpConns := map[string]*udpConn{ 114 "audio": {port: 4000, payloadType: 111}, 115 "video": {port: 4002, payloadType: 96}, 116 } 117 for _, c := range udpConns { 118 // Create remote addr 119 var raddr *net.UDPAddr 120 if raddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", c.port)); err != nil { 121 panic(err) 122 } 123 124 // Dial udp 125 if c.conn, err = net.DialUDP("udp", laddr, raddr); err != nil { 126 panic(err) 127 } 128 defer func(conn net.PacketConn) { 129 if closeErr := conn.Close(); closeErr != nil { 130 panic(closeErr) 131 } 132 }(c.conn) 133 } 134 135 // Set a handler for when a new remote track starts, this handler will forward data to 136 // our UDP listeners. 137 // In your application this is where you would handle/process audio/video 138 peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive 139 // Retrieve udp connection 140 c, ok := udpConns[track.Kind().String()] 141 if !ok { 142 return 143 } 144 145 b := make([]byte, 1500) 146 rtpPacket := &rtp.Packet{} 147 for { 148 // Read 149 n, _, readErr := track.Read(b) 150 if readErr != nil { 151 panic(readErr) 152 } 153 154 // Unmarshal the packet and update the PayloadType 155 if err = rtpPacket.Unmarshal(b[:n]); err != nil { 156 panic(err) 157 } 158 rtpPacket.PayloadType = c.payloadType 159 160 // Marshal into original buffer with updated PayloadType 161 if n, err = rtpPacket.MarshalTo(b); err != nil { 162 panic(err) 163 } 164 165 // Write 166 if _, writeErr := c.conn.Write(b[:n]); writeErr != nil { 167 // For this particular example, third party applications usually timeout after a short 168 // amount of time during which the user doesn't have enough time to provide the answer 169 // to the browser. 170 // That's why, for this particular example, the user first needs to provide the answer 171 // to the browser then open the third party application. Therefore we must not kill 172 // the forward on "connection refused" errors 173 var opError *net.OpError 174 if errors.As(writeErr, &opError) && opError.Err.Error() == "write: connection refused" { 175 continue 176 } 177 panic(err) 178 } 179 } 180 }) 181 182 // Set the handler for ICE connection state 183 // This will notify you when the peer has connected/disconnected 184 peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { 185 fmt.Printf("Connection State has changed %s \n", connectionState.String()) 186 187 if connectionState == webrtc.ICEConnectionStateConnected { 188 fmt.Println("Ctrl+C the remote client to stop the demo") 189 } 190 }) 191 192 // Set the handler for Peer connection state 193 // This will notify you when the peer has connected/disconnected 194 peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { 195 fmt.Printf("Peer Connection State has changed: %s\n", s.String()) 196 197 if s == webrtc.PeerConnectionStateFailed { 198 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 199 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 200 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 201 fmt.Println("Done forwarding") 202 os.Exit(0) 203 } 204 205 if s == webrtc.PeerConnectionStateClosed { 206 // PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify 207 fmt.Println("Done forwarding") 208 os.Exit(0) 209 } 210 }) 211 212 // Wait for the offer to be pasted 213 offer := webrtc.SessionDescription{} 214 decode(readUntilNewline(), &offer) 215 216 // Set the remote SessionDescription 217 if err = peerConnection.SetRemoteDescription(offer); err != nil { 218 panic(err) 219 } 220 221 // Create answer 222 answer, err := peerConnection.CreateAnswer(nil) 223 if err != nil { 224 panic(err) 225 } 226 227 // Create channel that is blocked until ICE Gathering is complete 228 gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 229 230 // Sets the LocalDescription, and starts our UDP listeners 231 if err = peerConnection.SetLocalDescription(answer); err != nil { 232 panic(err) 233 } 234 235 // Block until ICE Gathering is complete, disabling trickle ICE 236 // we do this because we only can exchange one signaling message 237 // in a production application you should exchange ICE Candidates via OnICECandidate 238 <-gatherComplete 239 240 // Output the answer in base64 so we can paste it in browser 241 fmt.Println(encode(peerConnection.LocalDescription())) 242 243 // Block forever 244 select {} 245 } 246 247 // Read from stdin until we get a newline 248 func readUntilNewline() (in string) { 249 var err error 250 251 r := bufio.NewReader(os.Stdin) 252 for { 253 in, err = r.ReadString('\n') 254 if err != nil && !errors.Is(err, io.EOF) { 255 panic(err) 256 } 257 258 if in = strings.TrimSpace(in); len(in) > 0 { 259 break 260 } 261 } 262 263 fmt.Println("") 264 return 265 } 266 267 // JSON encode + base64 a SessionDescription 268 func encode(obj *webrtc.SessionDescription) string { 269 b, err := json.Marshal(obj) 270 if err != nil { 271 panic(err) 272 } 273 274 return base64.StdEncoding.EncodeToString(b) 275 } 276 277 // Decode a base64 and unmarshal JSON into a SessionDescription 278 func decode(in string, obj *webrtc.SessionDescription) { 279 b, err := base64.StdEncoding.DecodeString(in) 280 if err != nil { 281 panic(err) 282 } 283 284 if err = json.Unmarshal(b, obj); err != nil { 285 panic(err) 286 } 287 }