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