mirror of
https://github.com/pion/webrtc.git
synced 2025-10-05 15:16:52 +08:00
Added examples/rtp-forwarder
Add new example that demonstrates how to take WebRTC to RTP. Also provides instructions and pre-canned SDP so you can easily playback in VLC and ffmpeg. Resolves #1061
This commit is contained in:

committed by
Sean DuBois

parent
d5998ae2dd
commit
38ee94e743
@@ -141,6 +141,7 @@ Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contribu
|
|||||||
* [lawl](https://github.com/lawl)
|
* [lawl](https://github.com/lawl)
|
||||||
* [Jorropo](https://github.com/Jorropo)
|
* [Jorropo](https://github.com/Jorropo)
|
||||||
* [Akil](https://github.com/akilude)
|
* [Akil](https://github.com/akilude)
|
||||||
|
* [Quentin Renard](https://github.com/asticode)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
MIT License - see [LICENSE](LICENSE) for full text
|
MIT License - see [LICENSE](LICENSE) for full text
|
||||||
|
@@ -12,6 +12,7 @@ For more full featured examples that use 3rd party libraries see our **[example-
|
|||||||
* [Play from disk](play-from-disk): The play-from-disk example demonstrates how to send video to your browser from a file saved to disk.
|
* [Play from disk](play-from-disk): The play-from-disk example demonstrates how to send video to your browser from a file saved to disk.
|
||||||
* [Save to Disk](save-to-disk): The save-to-disk example shows how to record your webcam and save the footage to disk on the server side.
|
* [Save to Disk](save-to-disk): The save-to-disk example shows how to record your webcam and save the footage to disk on the server side.
|
||||||
* [Broadcast](broadcast): The broadcast example demonstrates how to broadcast a video to multiple peers. A broadcaster uploads the video once and the server forwards it to all other peers.
|
* [Broadcast](broadcast): The broadcast example demonstrates how to broadcast a video to multiple peers. A broadcaster uploads the video once and the server forwards it to all other peers.
|
||||||
|
* [RTP Forwarder](rtp-forwarder): The rtp-forwarder example demonstrates how to forward your audio/video streams using RTP.
|
||||||
|
|
||||||
#### Data Channel API
|
#### Data Channel API
|
||||||
* [Data Channels](data-channels): The data-channels example shows how you can send/recv DataChannel messages from a web browser.
|
* [Data Channels](data-channels): The data-channels example shows how you can send/recv DataChannel messages from a web browser.
|
||||||
|
@@ -53,6 +53,12 @@
|
|||||||
"description": "The broadcast example demonstrates how to broadcast a video to multiple peers. A broadcaster uploads the video once and the server forwards it to all other peers.",
|
"description": "The broadcast example demonstrates how to broadcast a video to multiple peers. A broadcaster uploads the video once and the server forwards it to all other peers.",
|
||||||
"type": "browser"
|
"type": "browser"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "RTP Forwarder",
|
||||||
|
"link": "rtp-forwarder",
|
||||||
|
"description": "The rtp-forwarder example demonstrates how to forward your audio/video streams using RTP.",
|
||||||
|
"type": "browser"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Custom Logger",
|
"title": "Custom Logger",
|
||||||
"link": "#",
|
"link": "#",
|
||||||
|
32
examples/rtp-forwarder/README.md
Normal file
32
examples/rtp-forwarder/README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# rtp-forwarder
|
||||||
|
rtp-forwarder is a simple application that shows how to forward your webcam/microphone via RTP using Pion WebRTC.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
### Download rtp-forwarder
|
||||||
|
```
|
||||||
|
go get github.com/pion/webrtc/examples/rtp-forwarder
|
||||||
|
```
|
||||||
|
|
||||||
|
### Open rtp-forwarder example page
|
||||||
|
[jsfiddle.net](https://jsfiddle.net/sq69370h/) you should see your Webcam, two text-areas and a 'Start Session' button
|
||||||
|
|
||||||
|
### Run rtp-forwarder, with your browsers SessionDescription as stdin
|
||||||
|
In the jsfiddle the top textarea is your browser, copy that and:
|
||||||
|
#### Linux/macOS
|
||||||
|
Run `echo $BROWSER_SDP | rtp-forwarder`
|
||||||
|
#### Windows
|
||||||
|
1. Paste the SessionDescription into a file.
|
||||||
|
1. Run `rtp-forwarder < my_file`
|
||||||
|
|
||||||
|
### Input rtp-forwarder's SessionDescription into your browser
|
||||||
|
Copy the text that `rtp-forwarder` just emitted and copy into second text area
|
||||||
|
|
||||||
|
### Hit 'Start Session' in jsfiddle and enjoy your RTP forwarded stream!
|
||||||
|
#### VLC
|
||||||
|
Open `rtp-forwarder.sdp` with VLC and enjoy your live video!
|
||||||
|
|
||||||
|
### ffmpeg/ffprobe
|
||||||
|
Run `ffprobe -i rtp-forwarder.sdp -protocol_whitelist file,udp,rtp` to get more details about your streams
|
||||||
|
|
||||||
|
Run `ffplay -i rtp-forwarder.sdp -protocol_whitelist file,udp,rtp` to play your streams
|
||||||
|
|
4
examples/rtp-forwarder/jsfiddle/demo.css
Normal file
4
examples/rtp-forwarder/jsfiddle/demo.css
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
textarea {
|
||||||
|
width: 500px;
|
||||||
|
min-height: 75px;
|
||||||
|
}
|
5
examples/rtp-forwarder/jsfiddle/demo.details
Normal file
5
examples/rtp-forwarder/jsfiddle/demo.details
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
name: rtp-forwarder
|
||||||
|
description: Example of using Pion WebRTC to forward WebRTC streams via RTP
|
||||||
|
authors:
|
||||||
|
- Quentin Renard
|
14
examples/rtp-forwarder/jsfiddle/demo.html
Normal file
14
examples/rtp-forwarder/jsfiddle/demo.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
Browser base64 Session Description<br />
|
||||||
|
<textarea id="localSessionDescription" readonly="true"></textarea> <br />
|
||||||
|
|
||||||
|
Golang base64 Session Description<br />
|
||||||
|
<textarea id="remoteSessionDescription"></textarea> <br/>
|
||||||
|
<button onclick="window.startSession()"> Start Session </button><br />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
Video<br />
|
||||||
|
<video id="video1" width="160" height="120" autoplay muted></video> <br />
|
||||||
|
|
||||||
|
Logs<br />
|
||||||
|
<div id="logs"></div>
|
38
examples/rtp-forwarder/jsfiddle/demo.js
Normal file
38
examples/rtp-forwarder/jsfiddle/demo.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/* eslint-env browser */
|
||||||
|
|
||||||
|
let pc = new RTCPeerConnection({
|
||||||
|
iceServers: [
|
||||||
|
{
|
||||||
|
urls: 'stun:stun.l.google.com:19302'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
var log = msg => {
|
||||||
|
document.getElementById('logs').innerHTML += msg + '<br>'
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||||
|
.then(stream => {
|
||||||
|
pc.addStream(document.getElementById('video1').srcObject = stream)
|
||||||
|
pc.createOffer().then(d => pc.setLocalDescription(d)).catch(log)
|
||||||
|
}).catch(log)
|
||||||
|
|
||||||
|
pc.oniceconnectionstatechange = e => log(pc.iceConnectionState)
|
||||||
|
pc.onicecandidate = event => {
|
||||||
|
if (event.candidate === null) {
|
||||||
|
document.getElementById('localSessionDescription').value = btoa(JSON.stringify(pc.localDescription))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.startSession = () => {
|
||||||
|
let sd = document.getElementById('remoteSessionDescription').value
|
||||||
|
if (sd === '') {
|
||||||
|
return alert('Session Description must not be empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(atob(sd))))
|
||||||
|
} catch (e) {
|
||||||
|
alert(e)
|
||||||
|
}
|
||||||
|
}
|
170
examples/rtp-forwarder/main.go
Normal file
170
examples/rtp-forwarder/main.go
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pion/rtcp"
|
||||||
|
"github.com/pion/webrtc/v2"
|
||||||
|
"github.com/pion/webrtc/v2/examples/internal/signal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type udpConn struct {
|
||||||
|
conn *net.UDPConn
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create context
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// Create a MediaEngine object to configure the supported codec
|
||||||
|
m := webrtc.MediaEngine{}
|
||||||
|
|
||||||
|
// Setup the codecs you want to use.
|
||||||
|
// We'll use a VP8 codec but you can also define your own
|
||||||
|
m.RegisterCodec(webrtc.NewRTPOpusCodec(webrtc.DefaultPayloadTypeOpus, 48000))
|
||||||
|
m.RegisterCodec(webrtc.NewRTPVP8Codec(webrtc.DefaultPayloadTypeVP8, 90000))
|
||||||
|
|
||||||
|
// Create the API object with the MediaEngine
|
||||||
|
api := webrtc.NewAPI(webrtc.WithMediaEngine(m))
|
||||||
|
|
||||||
|
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
|
||||||
|
|
||||||
|
// Prepare the configuration
|
||||||
|
config := webrtc.Configuration{
|
||||||
|
ICEServers: []webrtc.ICEServer{
|
||||||
|
{
|
||||||
|
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new RTCPeerConnection
|
||||||
|
peerConnection, err := api.NewPeerConnection(config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow us to receive 1 audio track, and 1 video track
|
||||||
|
if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeAudio); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeVideo); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a local addr
|
||||||
|
var laddr *net.UDPAddr
|
||||||
|
if laddr, err = net.ResolveUDPAddr("udp", "127.0.0.1:"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare udp conns
|
||||||
|
udpConns := map[string]*udpConn{
|
||||||
|
"audio": {port: 4000},
|
||||||
|
"video": {port: 4002},
|
||||||
|
}
|
||||||
|
for _, c := range udpConns {
|
||||||
|
// Create remote addr
|
||||||
|
var raddr *net.UDPAddr
|
||||||
|
if raddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", c.port)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial udp
|
||||||
|
if c.conn, err = net.DialUDP("udp", laddr, raddr); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer func(conn net.PacketConn) {
|
||||||
|
if closeErr := conn.Close(); closeErr != nil {
|
||||||
|
panic(closeErr)
|
||||||
|
}
|
||||||
|
}(c.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a handler for when a new remote track starts, this handler will forward data to
|
||||||
|
// our UDP listeners.
|
||||||
|
// In your application this is where you would handle/process audio/video
|
||||||
|
peerConnection.OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
|
||||||
|
// Retrieve udp connection
|
||||||
|
c, ok := udpConns[track.Kind().String()]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Second * 2)
|
||||||
|
for range ticker.C {
|
||||||
|
if rtcpErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}}); rtcpErr != nil {
|
||||||
|
fmt.Println(rtcpErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
// Read
|
||||||
|
n, readErr := track.Read(b)
|
||||||
|
if readErr != nil {
|
||||||
|
panic(readErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
if _, err = c.conn.Write(b[:n]); err != nil {
|
||||||
|
// For this particular example, third party applications usually timeout after a short
|
||||||
|
// amount of time during which the user doesn't have enough time to provide the answer
|
||||||
|
// to the browser.
|
||||||
|
// That's why, for this particular example, the user first needs to provide the answer
|
||||||
|
// to the browser then open the third party application. Therefore we must not kill
|
||||||
|
// the forward on "connection refused" errors
|
||||||
|
if opError, ok := err.(*net.OpError); ok && opError.Err.Error() == "write: connection refused" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set the handler for ICE connection state
|
||||||
|
// This will notify you when the peer has connected/disconnected
|
||||||
|
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
|
||||||
|
fmt.Printf("Connection State has changed %s \n", connectionState.String())
|
||||||
|
|
||||||
|
if connectionState == webrtc.ICEConnectionStateConnected {
|
||||||
|
fmt.Println("Ctrl+C the remote client to stop the demo")
|
||||||
|
} else if connectionState == webrtc.ICEConnectionStateFailed ||
|
||||||
|
connectionState == webrtc.ICEConnectionStateDisconnected {
|
||||||
|
fmt.Println("Done forwarding")
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait for the offer to be pasted
|
||||||
|
offer := webrtc.SessionDescription{}
|
||||||
|
signal.Decode(signal.MustReadStdin(), &offer)
|
||||||
|
|
||||||
|
// Set the remote SessionDescription
|
||||||
|
if err = peerConnection.SetRemoteDescription(offer); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create answer
|
||||||
|
answer, err := peerConnection.CreateAnswer(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the LocalDescription, and starts our UDP listeners
|
||||||
|
if err = peerConnection.SetLocalDescription(answer); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the answer in base64 so we can paste it in browser
|
||||||
|
fmt.Println(signal.Encode(answer))
|
||||||
|
|
||||||
|
// Wait for context to be done
|
||||||
|
<-ctx.Done()
|
||||||
|
}
|
9
examples/rtp-forwarder/rtp-forwarder.sdp
Normal file
9
examples/rtp-forwarder/rtp-forwarder.sdp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
v=0
|
||||||
|
o=- 0 0 IN IP4 127.0.0.1
|
||||||
|
s=Pion WebRTC
|
||||||
|
c=IN IP4 127.0.0.1
|
||||||
|
t=0 0
|
||||||
|
m=audio 4000 RTP/AVP 111
|
||||||
|
a=rtpmap:111 OPUS/48000/2
|
||||||
|
m=video 4002 RTP/AVP 96
|
||||||
|
a=rtpmap:96 VP8/90000
|
Reference in New Issue
Block a user