diff --git a/examples/rtp/README.md b/examples/rtp/README.md new file mode 100644 index 0000000..fc80ff8 --- /dev/null +++ b/examples/rtp/README.md @@ -0,0 +1,30 @@ +## Instructions + +### Download rtp-send example + +``` +go get github.com/pion/mediadevices/examples/rtp-send +``` + +### Listen RTP + +Install GStreamer and run: +``` +gst-launch-1.0 udpsrc port=5000 caps=application/x-rtp,encode-name=VP8 \ + ! rtpvp8depay ! vp8dec ! videoconvert ! autovideosink +``` + +Or run VLC media plyer: +``` +vlc ./vp8.sdp +``` + +### Run rtp-send + +Run `rtp-send localhost:5000` + +A video should start playing in your GStreamer or VLC window. +It's not WebRTC, but pure RTP. + +Congrats, you have used pion-MediaDevices! Now start building something cool + diff --git a/examples/rtp/main.go b/examples/rtp/main.go new file mode 100644 index 0000000..22f1906 --- /dev/null +++ b/examples/rtp/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "net" + "os" + + "github.com/pion/mediadevices" + "github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder + _ "github.com/pion/mediadevices/pkg/driver/camera" // This is required to register camera adapter + "github.com/pion/mediadevices/pkg/frame" + "github.com/pion/mediadevices/pkg/prop" +) + +const ( + mtu = 1000 +) + +func must(err error) { + if err != nil { + panic(err) + } +} + +func main() { + if len(os.Args) != 2 { + fmt.Printf("usage: %s host:port\n", os.Args[0]) + return + } + dest := os.Args[1] + + vp8Params, err := vpx.NewVP8Params() + must(err) + vp8Params.BitRate = 100000 // 100kbps + + codecSelector := mediadevices.NewCodecSelector( + mediadevices.WithVideoEncoders(&vp8Params), + ) + + mediaStream, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{ + Video: func(c *mediadevices.MediaTrackConstraints) { + c.FrameFormat = prop.FrameFormat(frame.FormatYUY2) + c.Width = prop.Int(640) + c.Height = prop.Int(480) + }, + Codec: codecSelector, + }) + must(err) + + videoTrack := mediaStream.GetVideoTracks()[0].(*mediadevices.VideoTrack) + defer videoTrack.Close() + + rtpReader, err := videoTrack.NewRTPReader(vp8Params.RTPCodec().Name, mtu) + must(err) + + addr, err := net.ResolveUDPAddr("udp", dest) + must(err) + conn, err := net.DialUDP("udp", nil, addr) + must(err) + + buff := make([]byte, mtu) + for { + pkts, release, err := rtpReader.Read() + must(err) + + for _, pkt := range pkts { + n, err := pkt.MarshalTo(buff) + must(err) + + _, err = conn.Write(buff[:n]) + must(err) + } + + release() + } +} diff --git a/examples/rtp/vp8.sdp b/examples/rtp/vp8.sdp new file mode 100644 index 0000000..88d4baa --- /dev/null +++ b/examples/rtp/vp8.sdp @@ -0,0 +1,9 @@ +v=0 +o=- 1234567890 1234567890 IN IP4 0.0.0.0 +s=RTP-Send Example +i=Example +c=IN IP4 0.0.0.0 +t=0 0 +a=recvonly +m=video 5000 RTP/AVP 100 +a=rtpmap:100 VP8/90000 diff --git a/rtpreader.go b/rtpreader.go new file mode 100644 index 0000000..013b7d1 --- /dev/null +++ b/rtpreader.go @@ -0,0 +1,21 @@ +package mediadevices + +import "github.com/pion/rtp" + +type RTPReader interface { + Read() (pkts []*rtp.Packet, release func(), err error) + Close() error +} + +type rtpReaderImpl struct { + readFn func() ([]*rtp.Packet, func(), error) + closeFn func() error +} + +func (r *rtpReaderImpl) Read() ([]*rtp.Packet, func(), error) { + return r.readFn() +} + +func (r *rtpReaderImpl) Close() error { + return r.closeFn() +} diff --git a/track.go b/track.go index 3e83e7f..0f597c0 100644 --- a/track.go +++ b/track.go @@ -11,6 +11,7 @@ import ( "github.com/pion/mediadevices/pkg/io/audio" "github.com/pion/mediadevices/pkg/io/video" "github.com/pion/mediadevices/pkg/wave" + "github.com/pion/rtp" "github.com/pion/webrtc/v2" "github.com/pion/webrtc/v2/pkg/media" ) @@ -259,6 +260,41 @@ func (track *VideoTrack) Unbind(pc *webrtc.PeerConnection) error { return track.unbind(pc) } +func (track *VideoTrack) NewRTPReader(codecName string, mtu int) (RTPReader, error) { + reader := track.NewReader(false) + inputProp, err := detectCurrentVideoProp(track.Broadcaster) + if err != nil { + return nil, err + } + + encodedReader, selectedCodec, err := track.selector.selectVideoCodecByNames(reader, inputProp, codecName) + if err != nil { + return nil, err + } + + sample := newVideoSampler(selectedCodec.ClockRate) + + // FIXME: not sure the best way to get unique ssrc. We probably should have a global keeper that can generate a random ID and does book keeping? + packetizer := rtp.NewPacketizer(mtu, selectedCodec.PayloadType, rand.Uint32(), selectedCodec.Payloader, rtp.NewRandomSequencer(), selectedCodec.ClockRate) + + return &rtpReaderImpl{ + readFn: func() ([]*rtp.Packet, func(), error) { + encoded, release, err := encodedReader.Read() + if err != nil { + encodedReader.Close() + track.onError(err) + return nil, func() {}, err + } + defer release() + + samples := sample() + pkts := packetizer.Packetize(encoded, samples) + return pkts, release, err + }, + closeFn: encodedReader.Close, + }, nil +} + // AudioTrack is a specific track type that contains audio source which allows multiple readers to access, and // manipulate. type AudioTrack struct {