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 }