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