Skip to content

Instantly share code, notes, and snippets.

@Sean-Der
Created May 20, 2019 00:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sean-Der/12155aed115380dcbaacece5bf04df6f to your computer and use it in GitHub Desktop.
Save Sean-Der/12155aed115380dcbaacece5bf04df6f to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"image"
"image/color"
"io"
"os/exec"
"strconv"
"time"
"github.com/pion/example-webrtc-applications/internal/signal"
"github.com/pion/rtcp"
"github.com/pion/webrtc"
"github.com/pion/webrtc/pkg/media/ivfwriter"
"gocv.io/x/gocv"
)
const (
frameX = 960
frameY = 720
frameSize = frameX * frameY * 3
minimumArea = 3000
)
func main() {
ffmpeg := exec.Command("ffmpeg", "-i", "pipe:0", "-pix_fmt", "bgr24", "-s", strconv.Itoa(frameX)+"x"+strconv.Itoa(frameY), "-f", "rawvideo", "pipe:1") //nolint
ffmpegIn, _ := ffmpeg.StdinPipe()
ffmpegOut, _ := ffmpeg.StdoutPipe()
if err := ffmpeg.Start(); err != nil {
panic(err)
}
createWebRTCConn(ffmpegIn)
startGoCVMotionDetect(ffmpegOut)
}
// This was taken from the GoCV examples, the only change is we are taking a buffer from ffmpeg instead of webcam
// https://github.com/hybridgroup/gocv/blob/master/cmd/motion-detect/main.go
func startGoCVMotionDetect(ffmpegOut io.Reader) {
window := gocv.NewWindow("Motion Window")
defer window.Close() //nolint
img := gocv.NewMat()
defer img.Close() //nolint
imgDelta := gocv.NewMat()
defer imgDelta.Close() //nolint
imgThresh := gocv.NewMat()
defer imgThresh.Close() //nolint
mog2 := gocv.NewBackgroundSubtractorMOG2()
defer mog2.Close() //nolint
for {
buf := make([]byte, frameSize)
if _, err := io.ReadFull(ffmpegOut, buf); err != nil {
fmt.Println(err)
continue
}
img, _ := gocv.NewMatFromBytes(frameY, frameX, gocv.MatTypeCV8UC3, buf)
if img.Empty() {
continue
}
status := "Ready"
statusColor := color.RGBA{0, 255, 0, 0}
// first phase of cleaning up image, obtain foreground only
mog2.Apply(img, &imgDelta)
// remaining cleanup of the image to use for finding contours.
// first use threshold
gocv.Threshold(imgDelta, &imgThresh, 25, 255, gocv.ThresholdBinary)
// then dilate
kernel := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(3, 3))
defer kernel.Close() //nolint
gocv.Dilate(imgThresh, &imgThresh, kernel)
// now find contours
contours := gocv.FindContours(imgThresh, gocv.RetrievalExternal, gocv.ChainApproxSimple)
for i, c := range contours {
area := gocv.ContourArea(c)
if area < minimumArea {
continue
}
status = "Motion detected"
statusColor = color.RGBA{255, 0, 0, 0}
gocv.DrawContours(&img, contours, i, statusColor, 2)
rect := gocv.BoundingRect(c)
gocv.Rectangle(&img, rect, color.RGBA{0, 0, 255, 0}, 2)
}
gocv.PutText(&img, status, image.Pt(10, 20), gocv.FontHersheyPlain, 1.2, statusColor, 2)
window.IMShow(img)
if window.WaitKey(1) == 27 {
break
}
}
}
func createWebRTCConn(ffmpegIn io.Writer) {
ivfWriter, err := ivfwriter.NewWith(ffmpegIn)
if err != nil {
panic(err)
}
// 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 := webrtc.NewPeerConnection(config)
if err != nil {
panic(err)
}
if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeVideo); err != nil {
panic(err)
}
// Set a handler for when a new remote track starts, this handler copies inbound RTP packets,
// replaces the SSRC and sends them back
peerConnection.OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
// Send a PLI on an interval so that the publisher is pushing a keyframe every rtcpPLIInterval
// This is a temporary fix until we implement incoming RTCP events, then we would push a PLI only when a viewer requests it
go func() {
ticker := time.NewTicker(time.Second * 3)
for range ticker.C {
errSend := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: track.SSRC()}})
if errSend != nil {
fmt.Println(errSend)
}
}
}()
fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().Name)
for {
// Read RTP packets being sent to Pion
rtp, readErr := track.ReadRTP()
if readErr != nil {
panic(readErr)
}
if ivfWriterErr := ivfWriter.WriteRTP(rtp); ivfWriterErr != nil {
panic(ivfWriterErr)
}
}
})
// 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())
})
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
signal.Decode(signal.MustReadStdin(), &offer)
// Set the remote SessionDescription
err = peerConnection.SetRemoteDescription(offer)
if err != nil {
panic(err)
}
// Create an answer
answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
}
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Output the answer in base64 so we can paste it in browser
fmt.Println(signal.Encode(answer))
}
@Sean-Der
Copy link
Author

Open example page

jsfiddle.net you should see your Webcam, two text-areas and a 'Start Session' button

Run main.go with your browsers SessionDescription as stdin

In the jsfiddle the top textarea is your browser, copy that and:

Linux/macOS

Run echo $BROWSER_SDP | go run main.go

Windows

  1. Paste the SessionDescription into a file.
  2. Run go run main.go < my_file

Input main.go's SessionDescription into your browser

Copy the text that go run main.go just emitted and copy into second text area

Hit 'Start Session' in jsfiddle, enjoy your media!

Your video and/or audio should popup automatically, and will continue playing until you close the application

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment