github.com/pion/webrtc/v4@v4.0.1/examples/save-to-disk-av1/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 // save-to-disk-av1 is a simple application that shows how to save a video to disk using AV1. 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 20 "github.com/pion/interceptor" 21 "github.com/pion/interceptor/pkg/intervalpli" 22 "github.com/pion/webrtc/v4" 23 "github.com/pion/webrtc/v4/pkg/media" 24 "github.com/pion/webrtc/v4/pkg/media/ivfwriter" 25 ) 26 27 func saveToDisk(i media.Writer, track *webrtc.TrackRemote) { 28 defer func() { 29 if err := i.Close(); err != nil { 30 panic(err) 31 } 32 }() 33 34 for { 35 rtpPacket, _, err := track.ReadRTP() 36 if err != nil { 37 fmt.Println(err) 38 return 39 } 40 if err := i.WriteRTP(rtpPacket); err != nil { 41 fmt.Println(err) 42 return 43 } 44 } 45 } 46 47 func main() { 48 // Everything below is the Pion WebRTC API! Thanks for using it ❤️. 49 50 // Create a MediaEngine object to configure the supported codec 51 m := &webrtc.MediaEngine{} 52 53 // Setup the codecs you want to use. 54 // We'll use a VP8 and Opus but you can also define your own 55 if err := m.RegisterCodec(webrtc.RTPCodecParameters{ 56 RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeAV1, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil}, 57 PayloadType: 96, 58 }, webrtc.RTPCodecTypeVideo); err != nil { 59 panic(err) 60 } 61 62 // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 63 // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 64 // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 65 // for each PeerConnection. 66 i := &interceptor.Registry{} 67 68 // Register a intervalpli factory 69 // This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender. 70 // This makes our video seekable and more error resilent, but at a cost of lower picture quality and higher bitrates 71 // A real world application should process incoming RTCP packets from viewers and forward them to senders 72 intervalPliFactory, err := intervalpli.NewReceiverInterceptor() 73 if err != nil { 74 panic(err) 75 } 76 i.Add(intervalPliFactory) 77 78 // Use the default set of Interceptors 79 if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil { 80 panic(err) 81 } 82 83 // Create the API object with the MediaEngine 84 api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)) 85 86 // Prepare the configuration 87 config := webrtc.Configuration{} 88 89 // Create a new RTCPeerConnection 90 peerConnection, err := api.NewPeerConnection(config) 91 if err != nil { 92 panic(err) 93 } 94 95 // Allow us to receive 1 video track 96 if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil { 97 panic(err) 98 } 99 100 ivfFile, err := ivfwriter.New("output.ivf", ivfwriter.WithCodec(webrtc.MimeTypeAV1)) 101 if err != nil { 102 panic(err) 103 } 104 105 // Set a handler for when a new remote track starts, this handler saves buffers to disk as 106 // an ivf file, since we could have multiple video tracks we provide a counter. 107 // In your application this is where you would handle/process video 108 peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive 109 if strings.EqualFold(track.Codec().MimeType, webrtc.MimeTypeAV1) { 110 fmt.Println("Got AV1 track, saving to disk as output.ivf") 111 saveToDisk(ivfFile, track) 112 } 113 }) 114 115 // Set the handler for ICE connection state 116 // This will notify you when the peer has connected/disconnected 117 peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { 118 fmt.Printf("Connection State has changed %s \n", connectionState.String()) 119 120 if connectionState == webrtc.ICEConnectionStateConnected { 121 fmt.Println("Ctrl+C the remote client to stop the demo") 122 } else if connectionState == webrtc.ICEConnectionStateFailed || connectionState == webrtc.ICEConnectionStateClosed { 123 if closeErr := ivfFile.Close(); closeErr != nil { 124 panic(closeErr) 125 } 126 127 fmt.Println("Done writing media files") 128 129 // Gracefully shutdown the peer connection 130 if closeErr := peerConnection.Close(); closeErr != nil { 131 panic(closeErr) 132 } 133 134 os.Exit(0) 135 } 136 }) 137 138 // Wait for the offer to be pasted 139 offer := webrtc.SessionDescription{} 140 decode(readUntilNewline(), &offer) 141 142 // Set the remote SessionDescription 143 err = peerConnection.SetRemoteDescription(offer) 144 if err != nil { 145 panic(err) 146 } 147 148 // Create answer 149 answer, err := peerConnection.CreateAnswer(nil) 150 if err != nil { 151 panic(err) 152 } 153 154 // Create channel that is blocked until ICE Gathering is complete 155 gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 156 157 // Sets the LocalDescription, and starts our UDP listeners 158 err = peerConnection.SetLocalDescription(answer) 159 if err != nil { 160 panic(err) 161 } 162 163 // Block until ICE Gathering is complete, disabling trickle ICE 164 // we do this because we only can exchange one signaling message 165 // in a production application you should exchange ICE Candidates via OnICECandidate 166 <-gatherComplete 167 168 // Output the answer in base64 so we can paste it in browser 169 fmt.Println(encode(peerConnection.LocalDescription())) 170 171 // Block forever 172 select {} 173 } 174 175 // Read from stdin until we get a newline 176 func readUntilNewline() (in string) { 177 var err error 178 179 r := bufio.NewReader(os.Stdin) 180 for { 181 in, err = r.ReadString('\n') 182 if err != nil && !errors.Is(err, io.EOF) { 183 panic(err) 184 } 185 186 if in = strings.TrimSpace(in); len(in) > 0 { 187 break 188 } 189 } 190 191 fmt.Println("") 192 return 193 } 194 195 // JSON encode + base64 a SessionDescription 196 func encode(obj *webrtc.SessionDescription) string { 197 b, err := json.Marshal(obj) 198 if err != nil { 199 panic(err) 200 } 201 202 return base64.StdEncoding.EncodeToString(b) 203 } 204 205 // Decode a base64 and unmarshal JSON into a SessionDescription 206 func decode(in string, obj *webrtc.SessionDescription) { 207 b, err := base64.StdEncoding.DecodeString(in) 208 if err != nil { 209 panic(err) 210 } 211 212 if err = json.Unmarshal(b, obj); err != nil { 213 panic(err) 214 } 215 }