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