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 }