Open
Description
Chrome added H265 support recently, I'm trying to run gstreamer-send example with some small adjustments (see the code below), but it doesn't work. I do see packetsReceived and incoming RTP packets in chrome://webrtc-internals, but no frames received. Audio track works good. Video doesn't work in both Safari and Chrome
I've also updated the latest pion/webrtc to v4.1.1.
Should h265 video work with latest chrome and pion/webrtc?
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
// gstreamer-send is a simple application that shows how to send video to your browser using Pion WebRTC and GStreamer.
package main
import (
"bufio"
"encoding/base64"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"strings"
"github.com/go-gst/go-gst/gst"
"github.com/go-gst/go-gst/gst/app"
"github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media"
)
func main() {
audioSrc := flag.String("audio-src", "audiotestsrc", "GStreamer audio src")
videoSrc := flag.String("video-src", "videotestsrc", "GStreamer video src")
flag.Parse()
// Initialize GStreamer
gst.Init(nil)
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
// Prepare the configuration
config := webrtc.Configuration{}
// Create a new RTCPeerConnection
peerConnection, err := webrtc.NewPeerConnection(config)
if err != nil {
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())
})
// Create a audio track
audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "audio/opus"}, "audio", "pion1")
if err != nil {
panic(err)
}
_, err = peerConnection.AddTrack(audioTrack)
if err != nil {
panic(err)
}
// Create a video track
firstVideoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/H265"}, "video", "pion2")
if err != nil {
panic(err)
}
_, err = peerConnection.AddTrack(firstVideoTrack)
if err != nil {
panic(err)
}
// Create a second video track
secondVideoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: "video/H265"}, "video", "pion3")
if err != nil {
panic(err)
}
_, err = peerConnection.AddTrack(secondVideoTrack)
if err != nil {
panic(err)
}
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
decode(readUntilNewline(), &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)
}
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
<-gatherComplete
// Output the answer in base64 so we can paste it in browser
fmt.Println(encode(peerConnection.LocalDescription()))
// Start pushing buffers on these tracks
pipelineForCodec("opus", []*webrtc.TrackLocalStaticSample{audioTrack}, *audioSrc)
pipelineForCodec("h265", []*webrtc.TrackLocalStaticSample{firstVideoTrack, secondVideoTrack}, *videoSrc)
// Block forever
select {}
}
// Create the appropriate GStreamer pipeline depending on what codec we are working with
func pipelineForCodec(codecName string, tracks []*webrtc.TrackLocalStaticSample, pipelineSrc string) {
pipelineStr := "appsink name=appsink"
switch codecName {
case "vp8":
pipelineStr = pipelineSrc + " ! vp8enc error-resilient=partitions keyframe-max-dist=10 auto-alt-ref=true cpu-used=5 deadline=1 ! " + pipelineStr
case "vp9":
pipelineStr = pipelineSrc + " ! vp9enc ! " + pipelineStr
case "h264":
pipelineStr = pipelineSrc + " ! video/x-raw,format=I420 ! x264enc speed-preset=ultrafast tune=zerolatency key-int-max=20 ! video/x-h264,stream-format=byte-stream ! " + pipelineStr
case "h265":
pipelineStr = pipelineSrc + " ! video/x-raw,format=I420 ! x265enc speed-preset=ultrafast tune=zerolatency key-int-max=20 ! video/x-h265,stream-format=byte-stream ! " + pipelineStr
case "opus":
pipelineStr = pipelineSrc + " ! opusenc ! " + pipelineStr
case "pcmu":
pipelineStr = pipelineSrc + " ! audio/x-raw, rate=8000 ! mulawenc ! " + pipelineStr
case "pcma":
pipelineStr = pipelineSrc + " ! audio/x-raw, rate=8000 ! alawenc ! " + pipelineStr
default:
panic("Unhandled codec " + codecName) //nolint
}
pipeline, err := gst.NewPipelineFromString(pipelineStr)
if err != nil {
panic(err)
}
if err = pipeline.SetState(gst.StatePlaying); err != nil {
panic(err)
}
appSink, err := pipeline.GetElementByName("appsink")
if err != nil {
panic(err)
}
app.SinkFromElement(appSink).SetCallbacks(&app.SinkCallbacks{
NewSampleFunc: func(sink *app.Sink) gst.FlowReturn {
sample := sink.PullSample()
if sample == nil {
return gst.FlowEOS
}
buffer := sample.GetBuffer()
if buffer == nil {
return gst.FlowError
}
samples := buffer.Map(gst.MapRead).Bytes()
defer buffer.Unmap()
for _, t := range tracks {
if err := t.WriteSample(media.Sample{Data: samples, Duration: *buffer.Duration().AsDuration()}); err != nil {
panic(err) //nolint
}
}
return gst.FlowOK
},
})
}
// Read from stdin until we get a newline
func readUntilNewline() (in string) {
var err error
r := bufio.NewReader(os.Stdin)
for {
in, err = r.ReadString('\n')
if err != nil && !errors.Is(err, io.EOF) {
panic(err)
}
if in = strings.TrimSpace(in); len(in) > 0 {
break
}
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {
panic(err)
}
if err = json.Unmarshal(b, obj); err != nil {
panic(err)
}
}
Metadata
Metadata
Assignees
Labels
No labels