mirror of
				https://github.com/pion/webrtc.git
				synced 2025-10-31 18:52:55 +08:00 
			
		
		
		
	 09461d55a6
			
		
	
	09461d55a6
	
	
	
		
			
			Users find it frustrating that example code doesn't work out of tree. This makes copying the examples out of the repo easier. Relates to #1981
		
			
				
	
	
		
			288 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			288 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| //go:build !js
 | |
| // +build !js
 | |
| 
 | |
| // rtp-forwarder shows how to forward your webcam/microphone via RTP using Pion WebRTC.
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"encoding/base64"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/pion/interceptor"
 | |
| 	"github.com/pion/interceptor/pkg/intervalpli"
 | |
| 	"github.com/pion/rtp"
 | |
| 	"github.com/pion/webrtc/v4"
 | |
| )
 | |
| 
 | |
| type udpConn struct {
 | |
| 	conn        *net.UDPConn
 | |
| 	port        int
 | |
| 	payloadType uint8
 | |
| }
 | |
| 
 | |
| // nolint:gocognit
 | |
| func main() {
 | |
| 	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
 | |
| 
 | |
| 	// Create a MediaEngine object to configure the supported codec
 | |
| 	m := &webrtc.MediaEngine{}
 | |
| 
 | |
| 	// Setup the codecs you want to use.
 | |
| 	// We'll use a VP8 and Opus but you can also define your own
 | |
| 	if err := m.RegisterCodec(webrtc.RTPCodecParameters{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
 | |
| 	}, webrtc.RTPCodecTypeVideo); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	if err := m.RegisterCodec(webrtc.RTPCodecParameters{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
 | |
| 	}, webrtc.RTPCodecTypeAudio); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
 | |
| 	// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
 | |
| 	// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
 | |
| 	// for each PeerConnection.
 | |
| 	i := &interceptor.Registry{}
 | |
| 
 | |
| 	// Register a intervalpli factory
 | |
| 	// This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender.
 | |
| 	// This makes our video seekable and more error resilent, but at a cost of lower picture quality and higher bitrates
 | |
| 	// A real world application should process incoming RTCP packets from viewers and forward them to senders
 | |
| 	intervalPliFactory, err := intervalpli.NewReceiverInterceptor()
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	i.Add(intervalPliFactory)
 | |
| 
 | |
| 	// Use the default set of Interceptors
 | |
| 	if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// Create the API object with the MediaEngine
 | |
| 	api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
 | |
| 
 | |
| 	// Prepare the configuration
 | |
| 	config := webrtc.Configuration{
 | |
| 		ICEServers: []webrtc.ICEServer{
 | |
| 			{
 | |
| 				URLs: []string{"stun:stun.l.google.com:19302"},
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	// Create a new RTCPeerConnection
 | |
| 	peerConnection, err := api.NewPeerConnection(config)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 	defer func() {
 | |
| 		if cErr := peerConnection.Close(); cErr != nil {
 | |
| 			fmt.Printf("cannot close peerConnection: %v\n", cErr)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	// Allow us to receive 1 audio track, and 1 video track
 | |
| 	if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil {
 | |
| 		panic(err)
 | |
| 	} else if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// Create a local addr
 | |
| 	var laddr *net.UDPAddr
 | |
| 	if laddr, err = net.ResolveUDPAddr("udp", "127.0.0.1:"); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// Prepare udp conns
 | |
| 	// Also update incoming packets with expected PayloadType, the browser may use
 | |
| 	// a different value. We have to modify so our stream matches what rtp-forwarder.sdp expects
 | |
| 	udpConns := map[string]*udpConn{
 | |
| 		"audio": {port: 4000, payloadType: 111},
 | |
| 		"video": {port: 4002, payloadType: 96},
 | |
| 	}
 | |
| 	for _, c := range udpConns {
 | |
| 		// Create remote addr
 | |
| 		var raddr *net.UDPAddr
 | |
| 		if raddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", c.port)); err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 
 | |
| 		// Dial udp
 | |
| 		if c.conn, err = net.DialUDP("udp", laddr, raddr); err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 		defer func(conn net.PacketConn) {
 | |
| 			if closeErr := conn.Close(); closeErr != nil {
 | |
| 				panic(closeErr)
 | |
| 			}
 | |
| 		}(c.conn)
 | |
| 	}
 | |
| 
 | |
| 	// Set a handler for when a new remote track starts, this handler will forward data to
 | |
| 	// our UDP listeners.
 | |
| 	// In your application this is where you would handle/process audio/video
 | |
| 	peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive
 | |
| 		// Retrieve udp connection
 | |
| 		c, ok := udpConns[track.Kind().String()]
 | |
| 		if !ok {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		b := make([]byte, 1500)
 | |
| 		rtpPacket := &rtp.Packet{}
 | |
| 		for {
 | |
| 			// Read
 | |
| 			n, _, readErr := track.Read(b)
 | |
| 			if readErr != nil {
 | |
| 				panic(readErr)
 | |
| 			}
 | |
| 
 | |
| 			// Unmarshal the packet and update the PayloadType
 | |
| 			if err = rtpPacket.Unmarshal(b[:n]); err != nil {
 | |
| 				panic(err)
 | |
| 			}
 | |
| 			rtpPacket.PayloadType = c.payloadType
 | |
| 
 | |
| 			// Marshal into original buffer with updated PayloadType
 | |
| 			if n, err = rtpPacket.MarshalTo(b); err != nil {
 | |
| 				panic(err)
 | |
| 			}
 | |
| 
 | |
| 			// Write
 | |
| 			if _, writeErr := c.conn.Write(b[:n]); writeErr != nil {
 | |
| 				// For this particular example, third party applications usually timeout after a short
 | |
| 				// amount of time during which the user doesn't have enough time to provide the answer
 | |
| 				// to the browser.
 | |
| 				// That's why, for this particular example, the user first needs to provide the answer
 | |
| 				// to the browser then open the third party application. Therefore we must not kill
 | |
| 				// the forward on "connection refused" errors
 | |
| 				var opError *net.OpError
 | |
| 				if errors.As(writeErr, &opError) && opError.Err.Error() == "write: connection refused" {
 | |
| 					continue
 | |
| 				}
 | |
| 				panic(err)
 | |
| 			}
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	// Set the handler for ICE connection state
 | |
| 	// This will notify you when the peer has connected/disconnected
 | |
| 	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
 | |
| 		fmt.Printf("Connection State has changed %s \n", connectionState.String())
 | |
| 
 | |
| 		if connectionState == webrtc.ICEConnectionStateConnected {
 | |
| 			fmt.Println("Ctrl+C the remote client to stop the demo")
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	// Set the handler for Peer connection state
 | |
| 	// This will notify you when the peer has connected/disconnected
 | |
| 	peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
 | |
| 		fmt.Printf("Peer Connection State has changed: %s\n", s.String())
 | |
| 
 | |
| 		if s == webrtc.PeerConnectionStateFailed {
 | |
| 			// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
 | |
| 			// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
 | |
| 			// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
 | |
| 			fmt.Println("Done forwarding")
 | |
| 			os.Exit(0)
 | |
| 		}
 | |
| 
 | |
| 		if s == webrtc.PeerConnectionStateClosed {
 | |
| 			// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
 | |
| 			fmt.Println("Done forwarding")
 | |
| 			os.Exit(0)
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	// Wait for the offer to be pasted
 | |
| 	offer := webrtc.SessionDescription{}
 | |
| 	decode(readUntilNewline(), &offer)
 | |
| 
 | |
| 	// Set the remote SessionDescription
 | |
| 	if err = peerConnection.SetRemoteDescription(offer); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// Create answer
 | |
| 	answer, err := peerConnection.CreateAnswer(nil)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// Create channel that is blocked until ICE Gathering is complete
 | |
| 	gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
 | |
| 
 | |
| 	// Sets the LocalDescription, and starts our UDP listeners
 | |
| 	if err = peerConnection.SetLocalDescription(answer); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// Block until ICE Gathering is complete, disabling trickle ICE
 | |
| 	// we do this because we only can exchange one signaling message
 | |
| 	// in a production application you should exchange ICE Candidates via OnICECandidate
 | |
| 	<-gatherComplete
 | |
| 
 | |
| 	// Output the answer in base64 so we can paste it in browser
 | |
| 	fmt.Println(encode(peerConnection.LocalDescription()))
 | |
| 
 | |
| 	// Block forever
 | |
| 	select {}
 | |
| }
 | |
| 
 | |
| // Read from stdin until we get a newline
 | |
| func readUntilNewline() (in string) {
 | |
| 	var err error
 | |
| 
 | |
| 	r := bufio.NewReader(os.Stdin)
 | |
| 	for {
 | |
| 		in, err = r.ReadString('\n')
 | |
| 		if err != nil && !errors.Is(err, io.EOF) {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 
 | |
| 		if in = strings.TrimSpace(in); len(in) > 0 {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fmt.Println("")
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // JSON encode + base64 a SessionDescription
 | |
| func encode(obj *webrtc.SessionDescription) string {
 | |
| 	b, err := json.Marshal(obj)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	return base64.StdEncoding.EncodeToString(b)
 | |
| }
 | |
| 
 | |
| // Decode a base64 and unmarshal JSON into a SessionDescription
 | |
| func decode(in string, obj *webrtc.SessionDescription) {
 | |
| 	b, err := base64.StdEncoding.DecodeString(in)
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	if err = json.Unmarshal(b, obj); err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| }
 |