Skip to content

Commit 93c1918

Browse files
committed
Add ORTC Media examples
Resolves pion#379
1 parent 9eded4e commit 93c1918

File tree

3 files changed

+302
-15
lines changed

3 files changed

+302
-15
lines changed

examples/ortc-media/README.md

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# ortc-media
2+
ortc demonstrates Pion WebRTC's [ORTC](https://ortc.org/) capabilities. Instead of using the Session Description Protocol
3+
to configure and communicate ORTC provides APIs. Users then can implement signaling with whatever protocol they wish.
4+
ORTC can then be used to implement WebRTC. A ORTC implementation can parse/emit Session Description and act as a WebRTC
5+
implementation.
6+
7+
In this example we have defined a simple JSON based signaling protocol.
8+
9+
## Instructions
10+
### Create IVF named `output.ivf` that contains a VP8/VP9/AV1 track
11+
```
12+
ffmpeg -i $INPUT_FILE -g 30 -b:v 2M output.ivf
13+
```
14+
15+
**Note**: In the `ffmpeg` command which produces the .ivf file, the argument `-b:v 2M` specifies the video bitrate to be 2 megabits per second. We provide this default value to produce decent video quality, but if you experience problems with this configuration (such as dropped frames etc.), you can decrease this. See the [ffmpeg documentation](https://ffmpeg.org/ffmpeg.html#Options) for more information on the format of the value.
16+
17+
18+
### Download ortc-media
19+
```
20+
go install github.com/pion/webrtc/v4/examples/ortc-media@latest
21+
```
22+
23+
### Run first client as offerer
24+
`ortc-media -offer` this will emit a base64 message. Copy this message to your clipboard.
25+
26+
## Run the second client as answerer
27+
Run the second client. This should be launched with the message you copied in the previous step as stdin.
28+
29+
`echo BASE64_MESSAGE_YOU_COPIED | ortc-media`
30+
31+
This will emit another base64 message. Copy this new message.
32+
33+
## Send base64 message to first client via CURL
34+
35+
* Run `curl localhost:8080 -d "BASE64_MESSAGE_YOU_COPIED"`. `BASE64_MESSAGE_YOU_COPIED` is the value you copied in the last step.
36+
37+
### Enjoy
38+
The client that accepts media will print when it gets the first media packet. The SSRC will be different every run.
39+
40+
```
41+
Got RTP Packet with SSRC 3097857772
42+
```
43+
44+
Media packets will continue to flow until the end of the file has been reached.
45+
46+
Congrats, you have used Pion WebRTC! Now start building something cool

examples/ortc-media/main.go

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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+
"errors"
12+
"flag"
13+
"fmt"
14+
"io"
15+
"os"
16+
"time"
17+
18+
"github.com/pion/webrtc/v4"
19+
"github.com/pion/webrtc/v4/examples/internal/signal"
20+
"github.com/pion/webrtc/v4/pkg/media"
21+
"github.com/pion/webrtc/v4/pkg/media/ivfreader"
22+
)
23+
24+
const (
25+
videoFileName = "output.ivf"
26+
)
27+
28+
func main() {
29+
isOffer := flag.Bool("offer", false, "Act as the offerer if set")
30+
port := flag.Int("port", 8080, "http server port")
31+
flag.Parse()
32+
33+
// Everything below is the Pion WebRTC (ORTC) API! Thanks for using it ❤️.
34+
35+
// Prepare ICE gathering options
36+
iceOptions := webrtc.ICEGatherOptions{
37+
ICEServers: []webrtc.ICEServer{
38+
{URLs: []string{"stun:stun.l.google.com:19302"}},
39+
},
40+
}
41+
42+
// Use default Codecs
43+
m := &webrtc.MediaEngine{}
44+
if err := m.RegisterDefaultCodecs(); err != nil {
45+
panic(err)
46+
}
47+
48+
// Create an API object
49+
api := webrtc.NewAPI(webrtc.WithMediaEngine(m))
50+
51+
// Create the ICE gatherer
52+
gatherer, err := api.NewICEGatherer(iceOptions)
53+
if err != nil {
54+
panic(err)
55+
}
56+
57+
// Construct the ICE transport
58+
ice := api.NewICETransport(gatherer)
59+
60+
// Construct the DTLS transport
61+
dtls, err := api.NewDTLSTransport(ice, nil)
62+
if err != nil {
63+
panic(err)
64+
}
65+
66+
// Create a RTPSender or RTPReceiver
67+
var (
68+
rtpReceiver *webrtc.RTPReceiver
69+
rtpSendParameters webrtc.RTPSendParameters
70+
)
71+
72+
if *isOffer {
73+
// Open the video file
74+
file, err := os.Open(videoFileName)
75+
if err != nil {
76+
panic(err)
77+
}
78+
79+
// Read the header of the video file
80+
ivf, header, err := ivfreader.NewWith(file)
81+
if err != nil {
82+
panic(err)
83+
}
84+
85+
trackLocal := fourCCToTrack(header.FourCC)
86+
87+
// Create RTPSender to send our video file
88+
rtpSender, err := api.NewRTPSender(trackLocal, dtls)
89+
if err != nil {
90+
panic(err)
91+
}
92+
93+
rtpSendParameters = rtpSender.GetParameters()
94+
95+
if err = rtpSender.Send(rtpSendParameters); err != nil {
96+
panic(err)
97+
}
98+
99+
go writeFileToTrack(ivf, header, trackLocal)
100+
} else {
101+
if rtpReceiver, err = api.NewRTPReceiver(webrtc.RTPCodecTypeVideo, dtls); err != nil {
102+
panic(err)
103+
}
104+
}
105+
106+
gatherFinished := make(chan struct{})
107+
gatherer.OnLocalCandidate(func(i *webrtc.ICECandidate) {
108+
if i == nil {
109+
close(gatherFinished)
110+
}
111+
})
112+
113+
// Gather candidates
114+
if err = gatherer.Gather(); err != nil {
115+
panic(err)
116+
}
117+
118+
<-gatherFinished
119+
120+
iceCandidates, err := gatherer.GetLocalCandidates()
121+
if err != nil {
122+
panic(err)
123+
}
124+
125+
iceParams, err := gatherer.GetLocalParameters()
126+
if err != nil {
127+
panic(err)
128+
}
129+
130+
dtlsParams, err := dtls.GetLocalParameters()
131+
if err != nil {
132+
panic(err)
133+
}
134+
135+
s := Signal{
136+
ICECandidates: iceCandidates,
137+
ICEParameters: iceParams,
138+
DTLSParameters: dtlsParams,
139+
RTPSendParameters: rtpSendParameters,
140+
}
141+
142+
iceRole := webrtc.ICERoleControlled
143+
144+
// Exchange the information
145+
fmt.Println(signal.Encode(s))
146+
remoteSignal := Signal{}
147+
148+
if *isOffer {
149+
signalingChan := signal.HTTPSDPServer(*port)
150+
signal.Decode(<-signalingChan, &remoteSignal)
151+
152+
iceRole = webrtc.ICERoleControlling
153+
} else {
154+
signal.Decode(signal.MustReadStdin(), &remoteSignal)
155+
}
156+
157+
if err = ice.SetRemoteCandidates(remoteSignal.ICECandidates); err != nil {
158+
panic(err)
159+
}
160+
161+
// Start the ICE transport
162+
if err = ice.Start(nil, remoteSignal.ICEParameters, &iceRole); err != nil {
163+
panic(err)
164+
}
165+
166+
// Start the DTLS transport
167+
if err = dtls.Start(remoteSignal.DTLSParameters); err != nil {
168+
panic(err)
169+
}
170+
171+
if !*isOffer {
172+
if err = rtpReceiver.Receive(webrtc.RTPReceiveParameters{
173+
Encodings: []webrtc.RTPDecodingParameters{
174+
{
175+
RTPCodingParameters: remoteSignal.RTPSendParameters.Encodings[0].RTPCodingParameters,
176+
},
177+
},
178+
}); err != nil {
179+
panic(err)
180+
}
181+
182+
remoteTrack := rtpReceiver.Track()
183+
pkt, _, err := remoteTrack.ReadRTP()
184+
if err != nil {
185+
panic(err)
186+
}
187+
188+
fmt.Printf("Got RTP Packet with SSRC %d \n", pkt.SSRC)
189+
}
190+
191+
select {}
192+
}
193+
194+
// Given a FourCC value return a Track
195+
func fourCCToTrack(fourCC string) *webrtc.TrackLocalStaticSample {
196+
// Determine video codec
197+
var trackCodec string
198+
switch fourCC {
199+
case "AV01":
200+
trackCodec = webrtc.MimeTypeAV1
201+
case "VP90":
202+
trackCodec = webrtc.MimeTypeVP9
203+
case "VP80":
204+
trackCodec = webrtc.MimeTypeVP8
205+
default:
206+
panic(fmt.Sprintf("Unable to handle FourCC %s", fourCC))
207+
}
208+
209+
// Create a video Track with the codec of the file
210+
trackLocal, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: trackCodec}, "video", "pion")
211+
if err != nil {
212+
panic(err)
213+
}
214+
215+
return trackLocal
216+
217+
}
218+
219+
// Write a file to Track
220+
func writeFileToTrack(ivf *ivfreader.IVFReader, header *ivfreader.IVFFileHeader, track *webrtc.TrackLocalStaticSample) {
221+
ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
222+
for ; true; <-ticker.C {
223+
frame, _, err := ivf.ParseNextFrame()
224+
if errors.Is(err, io.EOF) {
225+
fmt.Printf("All video frames parsed and sent")
226+
os.Exit(0)
227+
}
228+
229+
if err != nil {
230+
panic(err)
231+
}
232+
233+
if err = track.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
234+
panic(err)
235+
}
236+
}
237+
}
238+
239+
// Signal is used to exchange signaling info.
240+
// This is not part of the ORTC spec. You are free
241+
// to exchange this information any way you want.
242+
type Signal struct {
243+
ICECandidates []webrtc.ICECandidate `json:"iceCandidates"`
244+
ICEParameters webrtc.ICEParameters `json:"iceParameters"`
245+
DTLSParameters webrtc.DTLSParameters `json:"dtlsParameters"`
246+
RTPSendParameters webrtc.RTPSendParameters `json:"rtpSendParameters"`
247+
}

examples/ortc/main.go

+9-15
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ func main() {
7070
})
7171

7272
// Gather candidates
73-
err = gatherer.Gather()
74-
if err != nil {
73+
if err = gatherer.Gather(); err != nil {
7574
panic(err)
7675
}
7776

@@ -101,24 +100,22 @@ func main() {
101100
SCTPCapabilities: sctpCapabilities,
102101
}
103102

103+
iceRole := webrtc.ICERoleControlled
104+
104105
// Exchange the information
105106
fmt.Println(signal.Encode(s))
106107
remoteSignal := Signal{}
107108

108109
if *isOffer {
109110
signalingChan := signal.HTTPSDPServer(*port)
110111
signal.Decode(<-signalingChan, &remoteSignal)
111-
} else {
112-
signal.Decode(signal.MustReadStdin(), &remoteSignal)
113-
}
114112

115-
iceRole := webrtc.ICERoleControlled
116-
if *isOffer {
117113
iceRole = webrtc.ICERoleControlling
114+
} else {
115+
signal.Decode(signal.MustReadStdin(), &remoteSignal)
118116
}
119117

120-
err = ice.SetRemoteCandidates(remoteSignal.ICECandidates)
121-
if err != nil {
118+
if err = ice.SetRemoteCandidates(remoteSignal.ICECandidates); err != nil {
122119
panic(err)
123120
}
124121

@@ -129,14 +126,12 @@ func main() {
129126
}
130127

131128
// Start the DTLS transport
132-
err = dtls.Start(remoteSignal.DTLSParameters)
133-
if err != nil {
129+
if err = dtls.Start(remoteSignal.DTLSParameters); err != nil {
134130
panic(err)
135131
}
136132

137133
// Start the SCTP transport
138-
err = sctp.Start(remoteSignal.SCTPCapabilities)
139-
if err != nil {
134+
if err = sctp.Start(remoteSignal.SCTPCapabilities); err != nil {
140135
panic(err)
141136
}
142137

@@ -183,8 +178,7 @@ func handleOnOpen(channel *webrtc.DataChannel) func() {
183178
message := signal.RandSeq(15)
184179
fmt.Printf("Sending '%s' \n", message)
185180

186-
err := channel.SendText(message)
187-
if err != nil {
181+
if err := channel.SendText(message); err != nil {
188182
panic(err)
189183
}
190184
}

0 commit comments

Comments
 (0)