github.com/pion/webrtc/v4@v4.0.1/examples/bandwidth-estimation-from-disk/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  // bandwidth-estimation-from-disk demonstrates how to use Pion's Bandwidth Estimation APIs.
     8  package main
     9  
    10  import (
    11  	"bufio"
    12  	"encoding/base64"
    13  	"encoding/json"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"os"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/pion/interceptor"
    22  	"github.com/pion/interceptor/pkg/cc"
    23  	"github.com/pion/interceptor/pkg/gcc"
    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  	lowFile    = "low.ivf"
    31  	lowBitrate = 300_000
    32  
    33  	medFile    = "med.ivf"
    34  	medBitrate = 1_000_000
    35  
    36  	highFile    = "high.ivf"
    37  	highBitrate = 2_500_000
    38  
    39  	ivfHeaderSize = 32
    40  )
    41  
    42  // nolint: gocognit
    43  func main() {
    44  	qualityLevels := []struct {
    45  		fileName string
    46  		bitrate  int
    47  	}{
    48  		{lowFile, lowBitrate},
    49  		{medFile, medBitrate},
    50  		{highFile, highBitrate},
    51  	}
    52  	currentQuality := 0
    53  
    54  	for _, level := range qualityLevels {
    55  		_, err := os.Stat(level.fileName)
    56  		if os.IsNotExist(err) {
    57  			panic(fmt.Sprintf("File %s was not found", level.fileName))
    58  		}
    59  	}
    60  
    61  	i := &interceptor.Registry{}
    62  	m := &webrtc.MediaEngine{}
    63  	if err := m.RegisterDefaultCodecs(); err != nil {
    64  		panic(err)
    65  	}
    66  
    67  	// Create a Congestion Controller. This analyzes inbound and outbound data and provides
    68  	// suggestions on how much we should be sending.
    69  	//
    70  	// Passing `nil` means we use the default Estimation Algorithm which is Google Congestion Control.
    71  	// You can use the other ones that Pion provides, or write your own!
    72  	congestionController, err := cc.NewInterceptor(func() (cc.BandwidthEstimator, error) {
    73  		return gcc.NewSendSideBWE(gcc.SendSideBWEInitialBitrate(lowBitrate))
    74  	})
    75  	if err != nil {
    76  		panic(err)
    77  	}
    78  
    79  	estimatorChan := make(chan cc.BandwidthEstimator, 1)
    80  	congestionController.OnNewPeerConnection(func(id string, estimator cc.BandwidthEstimator) { //nolint: revive
    81  		estimatorChan <- estimator
    82  	})
    83  
    84  	i.Add(congestionController)
    85  	if err = webrtc.ConfigureTWCCHeaderExtensionSender(m, i); err != nil {
    86  		panic(err)
    87  	}
    88  
    89  	if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
    90  		panic(err)
    91  	}
    92  
    93  	// Create a new RTCPeerConnection
    94  	peerConnection, err := webrtc.NewAPI(webrtc.WithInterceptorRegistry(i), webrtc.WithMediaEngine(m)).NewPeerConnection(webrtc.Configuration{
    95  		ICEServers: []webrtc.ICEServer{
    96  			{
    97  				URLs: []string{"stun:stun.l.google.com:19302"},
    98  			},
    99  		},
   100  	})
   101  	if err != nil {
   102  		panic(err)
   103  	}
   104  	defer func() {
   105  		if cErr := peerConnection.Close(); cErr != nil {
   106  			fmt.Printf("cannot close peerConnection: %v\n", cErr)
   107  		}
   108  	}()
   109  
   110  	// Wait until our Bandwidth Estimator has been created
   111  	estimator := <-estimatorChan
   112  
   113  	// Create a video track
   114  	videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion")
   115  	if err != nil {
   116  		panic(err)
   117  	}
   118  
   119  	rtpSender, err := peerConnection.AddTrack(videoTrack)
   120  	if err != nil {
   121  		panic(err)
   122  	}
   123  
   124  	// Read incoming RTCP packets
   125  	// Before these packets are returned they are processed by interceptors. For things
   126  	// like NACK this needs to be called.
   127  	go func() {
   128  		rtcpBuf := make([]byte, 1500)
   129  		for {
   130  			if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
   131  				return
   132  			}
   133  		}
   134  	}()
   135  
   136  	// Set the handler for ICE connection state
   137  	// This will notify you when the peer has connected/disconnected
   138  	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
   139  		fmt.Printf("Connection State has changed %s \n", connectionState.String())
   140  	})
   141  
   142  	// Set the handler for Peer connection state
   143  	// This will notify you when the peer has connected/disconnected
   144  	peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
   145  		fmt.Printf("Peer Connection State has changed: %s\n", s.String())
   146  	})
   147  
   148  	// Wait for the offer to be pasted
   149  	offer := webrtc.SessionDescription{}
   150  	decode(readUntilNewline(), &offer)
   151  
   152  	// Set the remote SessionDescription
   153  	if err = peerConnection.SetRemoteDescription(offer); err != nil {
   154  		panic(err)
   155  	}
   156  
   157  	// Create answer
   158  	answer, err := peerConnection.CreateAnswer(nil)
   159  	if err != nil {
   160  		panic(err)
   161  	}
   162  
   163  	// Create channel that is blocked until ICE Gathering is complete
   164  	gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
   165  
   166  	// Sets the LocalDescription, and starts our UDP listeners
   167  	if err = peerConnection.SetLocalDescription(answer); err != nil {
   168  		panic(err)
   169  	}
   170  
   171  	// Block until ICE Gathering is complete, disabling trickle ICE
   172  	// we do this because we only can exchange one signaling message
   173  	// in a production application you should exchange ICE Candidates via OnICECandidate
   174  	<-gatherComplete
   175  
   176  	// Output the answer in base64 so we can paste it in browser
   177  	fmt.Println(encode(peerConnection.LocalDescription()))
   178  
   179  	// Open a IVF file and start reading using our IVFReader
   180  	file, err := os.Open(qualityLevels[currentQuality].fileName)
   181  	if err != nil {
   182  		panic(err)
   183  	}
   184  
   185  	ivf, header, err := ivfreader.NewWith(file)
   186  	if err != nil {
   187  		panic(err)
   188  	}
   189  
   190  	// Send our video file frame at a time. Pace our sending so we send it at the same speed it should be played back as.
   191  	// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
   192  	//
   193  	// It is important to use a time.Ticker instead of time.Sleep because
   194  	// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
   195  	// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
   196  	ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
   197  	defer ticker.Stop()
   198  	frame := []byte{}
   199  	frameHeader := &ivfreader.IVFFrameHeader{}
   200  	currentTimestamp := uint64(0)
   201  
   202  	switchQualityLevel := func(newQualityLevel int) {
   203  		fmt.Printf("Switching from %s to %s \n", qualityLevels[currentQuality].fileName, qualityLevels[newQualityLevel].fileName)
   204  		currentQuality = newQualityLevel
   205  		ivf.ResetReader(setReaderFile(qualityLevels[currentQuality].fileName))
   206  		for {
   207  			if frame, frameHeader, err = ivf.ParseNextFrame(); err != nil {
   208  				break
   209  			} else if frameHeader.Timestamp >= currentTimestamp && frame[0]&0x1 == 0 {
   210  				break
   211  			}
   212  		}
   213  	}
   214  
   215  	for ; true; <-ticker.C {
   216  		targetBitrate := estimator.GetTargetBitrate()
   217  		switch {
   218  		// If current quality level is below target bitrate drop to level below
   219  		case currentQuality != 0 && targetBitrate < qualityLevels[currentQuality].bitrate:
   220  			switchQualityLevel(currentQuality - 1)
   221  
   222  			// If next quality level is above target bitrate move to next level
   223  		case len(qualityLevels) > (currentQuality+1) && targetBitrate > qualityLevels[currentQuality+1].bitrate:
   224  			switchQualityLevel(currentQuality + 1)
   225  
   226  		// Adjust outbound bandwidth for probing
   227  		default:
   228  			frame, _, err = ivf.ParseNextFrame()
   229  		}
   230  
   231  		switch {
   232  		// If we have reached the end of the file start again
   233  		case errors.Is(err, io.EOF):
   234  			ivf.ResetReader(setReaderFile(qualityLevels[currentQuality].fileName))
   235  
   236  		// No error write the video frame
   237  		case err == nil:
   238  			currentTimestamp = frameHeader.Timestamp
   239  			if err = videoTrack.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
   240  				panic(err)
   241  			}
   242  		// Error besides io.EOF that we dont know how to handle
   243  		default:
   244  			panic(err)
   245  		}
   246  	}
   247  }
   248  
   249  func setReaderFile(filename string) func(_ int64) io.Reader {
   250  	return func(_ int64) io.Reader {
   251  		file, err := os.Open(filename) // nolint
   252  		if err != nil {
   253  			panic(err)
   254  		}
   255  		if _, err = file.Seek(ivfHeaderSize, io.SeekStart); err != nil {
   256  			panic(err)
   257  		}
   258  		return file
   259  	}
   260  }
   261  
   262  // Read from stdin until we get a newline
   263  func readUntilNewline() (in string) {
   264  	var err error
   265  
   266  	r := bufio.NewReader(os.Stdin)
   267  	for {
   268  		in, err = r.ReadString('\n')
   269  		if err != nil && !errors.Is(err, io.EOF) {
   270  			panic(err)
   271  		}
   272  
   273  		if in = strings.TrimSpace(in); len(in) > 0 {
   274  			break
   275  		}
   276  	}
   277  
   278  	fmt.Println("")
   279  	return
   280  }
   281  
   282  // JSON encode + base64 a SessionDescription
   283  func encode(obj *webrtc.SessionDescription) string {
   284  	b, err := json.Marshal(obj)
   285  	if err != nil {
   286  		panic(err)
   287  	}
   288  
   289  	return base64.StdEncoding.EncodeToString(b)
   290  }
   291  
   292  // Decode a base64 and unmarshal JSON into a SessionDescription
   293  func decode(in string, obj *webrtc.SessionDescription) {
   294  	b, err := base64.StdEncoding.DecodeString(in)
   295  	if err != nil {
   296  		panic(err)
   297  	}
   298  
   299  	if err = json.Unmarshal(b, obj); err != nil {
   300  		panic(err)
   301  	}
   302  }