-
Notifications
You must be signed in to change notification settings - Fork 256
/
Copy pathmain.go
181 lines (150 loc) · 4.62 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
// snapshot shows how you can convert incoming video frames to jpeg and serve them via HTTP.
package main
import (
"bytes"
"encoding/json"
"fmt"
"image/jpeg"
"net/http"
"strconv"
"time"
"github.com/pion/rtcp"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media/samplebuilder"
"golang.org/x/image/vp8"
)
// Channel for PeerConnection to push RTP Packets
// This is the read from HTTP Handler for generating jpeg
var rtpChan chan *rtp.Packet // nolint:gochecknoglobals
func signaling(w http.ResponseWriter, r *http.Request) {
// Create a new PeerConnection
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
})
if err != nil {
panic(err)
}
// Set a handler for when a new remote track starts, this handler saves buffers to SampleBuilder
// so we can generate a snapshot
peerConnection.OnTrack(func(track *webrtc.TrackRemote, _ *webrtc.RTPReceiver) {
if track.Kind() == webrtc.RTPCodecTypeVideo {
// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
go func() {
ticker := time.NewTicker(time.Second * 3)
for range ticker.C {
errSend := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}})
if errSend != nil {
fmt.Println(errSend)
}
}
}()
}
for {
// Read RTP Packets in a loop
rtpPacket, _, readErr := track.ReadRTP()
if readErr != nil {
panic(readErr)
}
// Use a lossy channel to send packets to snapshot handler
// We don't want to block and queue up old data
select {
case rtpChan <- rtpPacket:
default:
}
}
})
// 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("ICE Connection State has changed: %s\n", connectionState.String())
})
var offer webrtc.SessionDescription
if err = json.NewDecoder(r.Body).Decode(&offer); err != nil {
panic(err)
}
if err = peerConnection.SetRemoteDescription(offer); err != nil {
panic(err)
}
answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
}
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
if err = peerConnection.SetLocalDescription(answer); err != nil {
panic(err)
}
// Block until ICE Gathering is complete, disabling trickle ICE
// we do this because we only can exchange one signaling message
// in a production application you should exchange ICE Candidates via OnICECandidate
<-gatherComplete
response, err := json.Marshal(*peerConnection.LocalDescription())
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(response); err != nil {
panic(err)
}
}
func snapshot(w http.ResponseWriter, _ *http.Request) {
// Initialized with 20 maxLate, my samples sometimes 10-15 packets
sampleBuilder := samplebuilder.New(20, &codecs.VP8Packet{}, 90000)
decoder := vp8.NewDecoder()
for {
// Pull RTP Packet from rtpChan
sampleBuilder.Push(<-rtpChan)
// Use SampleBuilder to generate full picture from many RTP Packets
sample := sampleBuilder.Pop()
if sample == nil {
continue
}
// Read VP8 header.
videoKeyframe := (sample.Data[0]&0x1 == 0)
if !videoKeyframe {
continue
}
// Begin VP8-to-image decode: Init->DecodeFrameHeader->DecodeFrame
decoder.Init(bytes.NewReader(sample.Data), len(sample.Data))
// Decode header
if _, err := decoder.DecodeFrameHeader(); err != nil {
panic(err)
}
// Decode Frame
img, err := decoder.DecodeFrame()
if err != nil {
panic(err)
}
// Encode to (RGB) jpeg
buffer := new(bytes.Buffer)
if err = jpeg.Encode(buffer, img, nil); err != nil {
panic(err)
}
// Serve image
w.Header().Set("Content-Type", "image/jpeg")
w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))
// Write jpeg as HTTP Response
if _, err = w.Write(buffer.Bytes()); err != nil {
panic(err)
}
return
}
}
func main() {
rtpChan = make(chan *rtp.Packet)
http.Handle("/", http.FileServer(http.Dir(".")))
http.HandleFunc("/signal", signaling)
http.HandleFunc("/snapshot", snapshot)
fmt.Println("Open http://localhost:8080 to access this demo")
panic(http.ListenAndServe(":8080", nil)) // nolint:gosec
}