github.com/pion/webrtc/v4@v4.0.1/examples/ortc-media/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  // ortc demonstrates Pion WebRTC's ORTC capabilities.
     8  package main
     9  
    10  import (
    11  	"bufio"
    12  	"encoding/base64"
    13  	"encoding/json"
    14  	"errors"
    15  	"flag"
    16  	"fmt"
    17  	"io"
    18  	"net/http"
    19  	"os"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/pion/webrtc/v4"
    25  	"github.com/pion/webrtc/v4/pkg/media"
    26  	"github.com/pion/webrtc/v4/pkg/media/ivfreader"
    27  )
    28  
    29  const (
    30  	videoFileName = "output.ivf"
    31  )
    32  
    33  func main() {
    34  	isOffer := flag.Bool("offer", false, "Act as the offerer if set")
    35  	port := flag.Int("port", 8080, "http server port")
    36  	flag.Parse()
    37  
    38  	// Everything below is the Pion WebRTC (ORTC) API! Thanks for using it ❤️.
    39  
    40  	// Prepare ICE gathering options
    41  	iceOptions := webrtc.ICEGatherOptions{
    42  		ICEServers: []webrtc.ICEServer{
    43  			{URLs: []string{"stun:stun.l.google.com:19302"}},
    44  		},
    45  	}
    46  
    47  	// Use default Codecs
    48  	m := &webrtc.MediaEngine{}
    49  	if err := m.RegisterDefaultCodecs(); err != nil {
    50  		panic(err)
    51  	}
    52  
    53  	// Create an API object
    54  	api := webrtc.NewAPI(webrtc.WithMediaEngine(m))
    55  
    56  	// Create the ICE gatherer
    57  	gatherer, err := api.NewICEGatherer(iceOptions)
    58  	if err != nil {
    59  		panic(err)
    60  	}
    61  
    62  	// Construct the ICE transport
    63  	ice := api.NewICETransport(gatherer)
    64  
    65  	// Construct the DTLS transport
    66  	dtls, err := api.NewDTLSTransport(ice, nil)
    67  	if err != nil {
    68  		panic(err)
    69  	}
    70  
    71  	// Create a RTPSender or RTPReceiver
    72  	var (
    73  		rtpReceiver       *webrtc.RTPReceiver
    74  		rtpSendParameters webrtc.RTPSendParameters
    75  	)
    76  
    77  	if *isOffer {
    78  		// Open the video file
    79  		file, fileErr := os.Open(videoFileName)
    80  		if fileErr != nil {
    81  			panic(fileErr)
    82  		}
    83  
    84  		// Read the header of the video file
    85  		ivf, header, fileErr := ivfreader.NewWith(file)
    86  		if fileErr != nil {
    87  			panic(fileErr)
    88  		}
    89  
    90  		trackLocal := fourCCToTrack(header.FourCC)
    91  
    92  		// Create RTPSender to send our video file
    93  		rtpSender, fileErr := api.NewRTPSender(trackLocal, dtls)
    94  		if fileErr != nil {
    95  			panic(fileErr)
    96  		}
    97  
    98  		rtpSendParameters = rtpSender.GetParameters()
    99  
   100  		if fileErr = rtpSender.Send(rtpSendParameters); fileErr != nil {
   101  			panic(fileErr)
   102  		}
   103  
   104  		go writeFileToTrack(ivf, header, trackLocal)
   105  	} else {
   106  		if rtpReceiver, err = api.NewRTPReceiver(webrtc.RTPCodecTypeVideo, dtls); err != nil {
   107  			panic(err)
   108  		}
   109  	}
   110  
   111  	gatherFinished := make(chan struct{})
   112  	gatherer.OnLocalCandidate(func(i *webrtc.ICECandidate) {
   113  		if i == nil {
   114  			close(gatherFinished)
   115  		}
   116  	})
   117  
   118  	// Gather candidates
   119  	if err = gatherer.Gather(); err != nil {
   120  		panic(err)
   121  	}
   122  
   123  	<-gatherFinished
   124  
   125  	iceCandidates, err := gatherer.GetLocalCandidates()
   126  	if err != nil {
   127  		panic(err)
   128  	}
   129  
   130  	iceParams, err := gatherer.GetLocalParameters()
   131  	if err != nil {
   132  		panic(err)
   133  	}
   134  
   135  	dtlsParams, err := dtls.GetLocalParameters()
   136  	if err != nil {
   137  		panic(err)
   138  	}
   139  
   140  	s := Signal{
   141  		ICECandidates:     iceCandidates,
   142  		ICEParameters:     iceParams,
   143  		DTLSParameters:    dtlsParams,
   144  		RTPSendParameters: rtpSendParameters,
   145  	}
   146  
   147  	iceRole := webrtc.ICERoleControlled
   148  
   149  	// Exchange the information
   150  	fmt.Println(encode(&s))
   151  	remoteSignal := Signal{}
   152  
   153  	if *isOffer {
   154  		signalingChan := httpSDPServer(*port)
   155  		decode(<-signalingChan, &remoteSignal)
   156  
   157  		iceRole = webrtc.ICERoleControlling
   158  	} else {
   159  		decode(readUntilNewline(), &remoteSignal)
   160  	}
   161  
   162  	if err = ice.SetRemoteCandidates(remoteSignal.ICECandidates); err != nil {
   163  		panic(err)
   164  	}
   165  
   166  	// Start the ICE transport
   167  	if err = ice.Start(nil, remoteSignal.ICEParameters, &iceRole); err != nil {
   168  		panic(err)
   169  	}
   170  
   171  	// Start the DTLS transport
   172  	if err = dtls.Start(remoteSignal.DTLSParameters); err != nil {
   173  		panic(err)
   174  	}
   175  
   176  	if !*isOffer {
   177  		if err = rtpReceiver.Receive(webrtc.RTPReceiveParameters{
   178  			Encodings: []webrtc.RTPDecodingParameters{
   179  				{
   180  					RTPCodingParameters: remoteSignal.RTPSendParameters.Encodings[0].RTPCodingParameters,
   181  				},
   182  			},
   183  		}); err != nil {
   184  			panic(err)
   185  		}
   186  
   187  		remoteTrack := rtpReceiver.Track()
   188  		pkt, _, err := remoteTrack.ReadRTP()
   189  		if err != nil {
   190  			panic(err)
   191  		}
   192  
   193  		fmt.Printf("Got RTP Packet with SSRC %d \n", pkt.SSRC)
   194  	}
   195  
   196  	select {}
   197  }
   198  
   199  // Given a FourCC value return a Track
   200  func fourCCToTrack(fourCC string) *webrtc.TrackLocalStaticSample {
   201  	// Determine video codec
   202  	var trackCodec string
   203  	switch fourCC {
   204  	case "AV01":
   205  		trackCodec = webrtc.MimeTypeAV1
   206  	case "VP90":
   207  		trackCodec = webrtc.MimeTypeVP9
   208  	case "VP80":
   209  		trackCodec = webrtc.MimeTypeVP8
   210  	default:
   211  		panic(fmt.Sprintf("Unable to handle FourCC %s", fourCC))
   212  	}
   213  
   214  	// Create a video Track with the codec of the file
   215  	trackLocal, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: trackCodec}, "video", "pion")
   216  	if err != nil {
   217  		panic(err)
   218  	}
   219  
   220  	return trackLocal
   221  }
   222  
   223  // Write a file to Track
   224  func writeFileToTrack(ivf *ivfreader.IVFReader, header *ivfreader.IVFFileHeader, track *webrtc.TrackLocalStaticSample) {
   225  	ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
   226  	defer ticker.Stop()
   227  	for ; true; <-ticker.C {
   228  		frame, _, err := ivf.ParseNextFrame()
   229  		if errors.Is(err, io.EOF) {
   230  			fmt.Printf("All video frames parsed and sent")
   231  			os.Exit(0) //nolint: gocritic
   232  		}
   233  
   234  		if err != nil {
   235  			panic(err)
   236  		}
   237  
   238  		if err = track.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
   239  			panic(err)
   240  		}
   241  	}
   242  }
   243  
   244  // Signal is used to exchange signaling info.
   245  // This is not part of the ORTC spec. You are free
   246  // to exchange this information any way you want.
   247  type Signal struct {
   248  	ICECandidates     []webrtc.ICECandidate    `json:"iceCandidates"`
   249  	ICEParameters     webrtc.ICEParameters     `json:"iceParameters"`
   250  	DTLSParameters    webrtc.DTLSParameters    `json:"dtlsParameters"`
   251  	RTPSendParameters webrtc.RTPSendParameters `json:"rtpSendParameters"`
   252  }
   253  
   254  // Read from stdin until we get a newline
   255  func readUntilNewline() (in string) {
   256  	var err error
   257  
   258  	r := bufio.NewReader(os.Stdin)
   259  	for {
   260  		in, err = r.ReadString('\n')
   261  		if err != nil && !errors.Is(err, io.EOF) {
   262  			panic(err)
   263  		}
   264  
   265  		if in = strings.TrimSpace(in); len(in) > 0 {
   266  			break
   267  		}
   268  	}
   269  
   270  	fmt.Println("")
   271  	return
   272  }
   273  
   274  // JSON encode + base64 a SessionDescription
   275  func encode(obj *Signal) string {
   276  	b, err := json.Marshal(obj)
   277  	if err != nil {
   278  		panic(err)
   279  	}
   280  
   281  	return base64.StdEncoding.EncodeToString(b)
   282  }
   283  
   284  // Decode a base64 and unmarshal JSON into a SessionDescription
   285  func decode(in string, obj *Signal) {
   286  	b, err := base64.StdEncoding.DecodeString(in)
   287  	if err != nil {
   288  		panic(err)
   289  	}
   290  
   291  	if err = json.Unmarshal(b, obj); err != nil {
   292  		panic(err)
   293  	}
   294  }
   295  
   296  // httpSDPServer starts a HTTP Server that consumes SDPs
   297  func httpSDPServer(port int) chan string {
   298  	sdpChan := make(chan string)
   299  	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   300  		body, _ := io.ReadAll(r.Body)
   301  		fmt.Fprintf(w, "done") //nolint: errcheck
   302  		sdpChan <- string(body)
   303  	})
   304  
   305  	go func() {
   306  		// nolint: gosec
   307  		panic(http.ListenAndServe(":"+strconv.Itoa(port), nil))
   308  	}()
   309  
   310  	return sdpChan
   311  }