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