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  }