Merge pull request #113 from go-gst/webrtc_support

initial Webrtc(-bin) support
This commit is contained in:
Wilhelm Bartel
2024-08-26 17:17:13 +02:00
committed by GitHub
18 changed files with 986 additions and 12 deletions

2
go.mod
View File

@@ -4,6 +4,6 @@ go 1.22
require github.com/mattn/go-pointer v0.0.1
require github.com/go-gst/go-glib v1.1.0
require github.com/go-gst/go-glib v1.2.0
require golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect

2
go.sum
View File

@@ -1,5 +1,7 @@
github.com/go-gst/go-glib v1.1.0 h1:XTGhwk2BWYjW/UZ08y7ojf3iPPRiYtXL0W6vJkXNKFc=
github.com/go-gst/go-glib v1.1.0/go.mod h1:JybIYeoHNwCkHGaBf1fHNIaM4sQTrJPkPLsi7dmPNOU=
github.com/go-gst/go-glib v1.2.0 h1:IEi5Og63V8YHBprCFiLsesRKSKWuxY0nYOMgbm7P2NI=
github.com/go-gst/go-glib v1.2.0/go.mod h1:JybIYeoHNwCkHGaBf1fHNIaM4sQTrJPkPLsi7dmPNOU=
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=

View File

@@ -198,5 +198,11 @@ func marshalPromise(p unsafe.Pointer) (interface{}, error) {
done: nil, // cannot be awaited if received from FFI
}
prom.Ref()
runtime.SetFinalizer(prom, func(p *Promise) {
p.Unref()
})
return prom, nil
}

View File

@@ -4,10 +4,8 @@ import (
"context"
"errors"
"runtime"
"sync"
"testing"
"time"
"unsafe"
)
//go:noinline
@@ -35,9 +33,7 @@ func awaitGC() {
}
func TestPromise(t *testing.T) {
initOnce.Do(func() {
Init(nil)
})
Init(nil)
prom := NewPromise()
cprom := prom.Instance()
@@ -87,12 +83,8 @@ func TestPromise(t *testing.T) {
awaitGC()
}
var initOnce sync.Once
func TestPromiseMarshal(t *testing.T) {
initOnce.Do(func() {
Init(nil)
})
Init(nil)
prom := NewPromise()
@@ -102,7 +94,7 @@ func TestPromiseMarshal(t *testing.T) {
t.Fatal(err)
}
receivedPromI, err := marshalPromise(unsafe.Pointer(gv.GValue))
receivedPromI, err := gv.GoValue()
if err != nil {
t.Fatal(err)

2
gst/gstsdp/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// gstsdp contains bindings for the gstreamer sdp library. See also https://gstreamer.freedesktop.org/documentation/sdp/index.html
package gstsdp

6
gst/gstsdp/gst.go.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef __GST_WEBRTC_GO_H__
#define __GST_WEBRTC_GO_H__
#include <gst/sdp/sdp.h>
#endif

94
gst/gstsdp/message.go Normal file
View File

@@ -0,0 +1,94 @@
package gstsdp
// #include "gst.go.h"
import "C"
import (
"errors"
"runtime"
"unsafe"
)
type SDPResult C.GstSDPResult
const (
SDPResultOk SDPResult = C.GST_SDP_OK
SDPEinval SDPResult = C.GST_SDP_EINVAL
)
type Message struct {
ptr *C.GstSDPMessage
}
func wrapSDPMessageAndFinalize(sdp *C.GstSDPMessage) *Message {
msg := &Message{
ptr: sdp,
}
// this requires that we copy the SDP message before passing it to any transfer-ownership function
runtime.SetFinalizer(msg, func(msg *Message) {
msg.Free()
})
return msg
}
// NewMessageFromUnsafe creates a new SDP message from a pointer and does not finalize it
func NewMessageFromUnsafe(ptr unsafe.Pointer) *Message {
return &Message{
ptr: (*C.GstSDPMessage)(ptr),
}
}
var ErrSDPInvalid = errors.New("invalid SDP")
func ParseSDPMessage(sdp string) (*Message, error) {
cstr := C.CString(sdp)
defer C.free(unsafe.Pointer(cstr))
var msg *C.GstSDPMessage
res := SDPResult(C.gst_sdp_message_new_from_text(cstr, &msg))
if res != SDPResultOk || msg == nil {
return nil, ErrSDPInvalid
}
return wrapSDPMessageAndFinalize(msg), nil
}
func (msg *Message) String() string {
cstr := C.gst_sdp_message_as_text(msg.ptr)
defer C.free(unsafe.Pointer(cstr))
return C.GoString(cstr)
}
// UnownedCopy creates a new copy of the SDP message that will not be finalized
//
// this is needed to pass the message back to C where C takes ownership of the message
//
// the returned SDP message will leak memory if not freed manually
func (msg *Message) UnownedCopy() *Message {
var newMsg *C.GstSDPMessage
res := C.gst_sdp_message_copy(msg.ptr, &newMsg)
if res != C.GST_SDP_OK || newMsg == nil {
return nil
}
return &Message{
ptr: newMsg,
}
}
// Free frees the SDP message.
//
// This is called automatically when the object is garbage collected.
func (msg *Message) Free() {
C.gst_sdp_message_free(msg.ptr)
msg.ptr = nil
}
func (msg *Message) Instance() unsafe.Pointer {
return unsafe.Pointer(msg.ptr)
}

7
gst/gstsdp/pkg_config.go Normal file
View File

@@ -0,0 +1,7 @@
package gstsdp
/*
#cgo pkg-config: gstreamer-sdp-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
*/
import "C"

View File

@@ -0,0 +1,67 @@
package gstwebrtc
// #include "gst.go.h"
import "C"
import (
"errors"
"unsafe"
"github.com/go-gst/go-glib/glib"
)
func init() {
tm := []glib.TypeMarshaler{
{T: glib.Type(C.GST_TYPE_WEBRTC_DATA_CHANNEL), F: marshalDataChannel},
}
glib.RegisterGValueMarshalers(tm)
}
// DataChannel is a representation of GstWebRTCDataChannel. See https://gstreamer.freedesktop.org/documentation/webrtclib/gstwebrtc-datachannel.html?gi-language=c
//
// there is no constructor for DataChannel, you can get it from webrtcbin signals
type DataChannel struct {
*glib.Object
}
func (dc *DataChannel) Close() {
C.gst_webrtc_data_channel_close((*C.GstWebRTCDataChannel)(dc.Native()))
}
func (dc *DataChannel) SendData(data []byte) error {
var gerr *C.GError
addr := unsafe.SliceData(data)
cbytes := C.g_bytes_new(C.gconstpointer(addr), C.gsize(len(data)))
defer C.g_bytes_unref(cbytes)
C.gst_webrtc_data_channel_send_data_full((*C.GstWebRTCDataChannel)(dc.Native()), cbytes, &gerr)
if gerr != nil {
defer C.g_error_free((*C.GError)(gerr))
errMsg := C.GoString(gerr.message)
return errors.New(errMsg)
}
return nil
}
// ToGValue implements glib.ValueTransformer
func (dc *DataChannel) ToGValue() (*glib.Value, error) {
val, err := glib.ValueInit(glib.Type(C.GST_TYPE_WEBRTC_DATA_CHANNEL))
if err != nil {
return nil, err
}
val.SetInstance(unsafe.Pointer(dc.GObject))
return val, nil
}
func marshalDataChannel(p unsafe.Pointer) (interface{}, error) {
c := C.g_value_get_object((*C.GValue)(p))
return &DataChannel{
Object: glib.Take(unsafe.Pointer(c)),
}, nil
}

View File

@@ -0,0 +1,42 @@
package gstwebrtc
import (
"testing"
"github.com/go-gst/go-gst/gst"
)
func TestDataChannelMarshal(t *testing.T) {
gst.Init(nil)
// hack to get a valid glib.Object
el, err := gst.NewElement("webrtcbin")
if err != nil {
t.Error(err)
}
dc := &DataChannel{
Object: el.Object.Object,
}
gv, err := dc.ToGValue()
if err != nil {
t.Error(err)
}
dcI, err := gv.GoValue()
if err != nil {
t.Error(err)
}
dc, ok := dcI.(*DataChannel)
if !ok {
t.Error("Failed to convert to DataChannel")
}
_ = dc
}

2
gst/gstwebrtc/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// gstwebrtc contains bindings for the gstreamer webrtclib. See also https://gstreamer.freedesktop.org/documentation/webrtclib/index.html
package gstwebrtc

507
gst/gstwebrtc/enums.go Normal file
View File

@@ -0,0 +1,507 @@
package gstwebrtc
// #include "gst.go.h"
import "C"
type BundlePolicy C.GstWebRTCBundlePolicy
const (
BUNDLE_POLICY_NONE BundlePolicy = C.GST_WEBRTC_BUNDLE_POLICY_NONE // none
BUNDLE_POLICY_BALANCED BundlePolicy = C.GST_WEBRTC_BUNDLE_POLICY_BALANCED // balanced
BUNDLE_POLICY_MAX_COMPAT BundlePolicy = C.GST_WEBRTC_BUNDLE_POLICY_MAX_COMPAT // max-compat
BUNDLE_POLICY_MAX_BUNDLE BundlePolicy = C.GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE // max-bundle
)
func (e BundlePolicy) String() string {
switch e {
case C.GST_WEBRTC_BUNDLE_POLICY_NONE:
return "none"
case C.GST_WEBRTC_BUNDLE_POLICY_BALANCED:
return "balanced"
case C.GST_WEBRTC_BUNDLE_POLICY_MAX_COMPAT:
return "max-compat"
case C.GST_WEBRTC_BUNDLE_POLICY_MAX_BUNDLE:
return "max-bundle"
}
return "unknown"
}
type DTLSSetup C.GstWebRTCDTLSSetup
const (
DTLS_SETUP_NONE DTLSSetup = C.GST_WEBRTC_DTLS_SETUP_NONE // none
DTLS_SETUP_ACTPASS DTLSSetup = C.GST_WEBRTC_DTLS_SETUP_ACTPASS // actpass
DTLS_SETUP_ACTIVE DTLSSetup = C.GST_WEBRTC_DTLS_SETUP_ACTIVE // sendonly
DTLS_SETUP_PASSIVE DTLSSetup = C.GST_WEBRTC_DTLS_SETUP_PASSIVE // recvonly
)
func (e DTLSSetup) String() string {
switch e {
case C.GST_WEBRTC_DTLS_SETUP_NONE:
return "none"
case C.GST_WEBRTC_DTLS_SETUP_ACTPASS:
return "actpass"
case C.GST_WEBRTC_DTLS_SETUP_ACTIVE:
return "sendonly"
case C.GST_WEBRTC_DTLS_SETUP_PASSIVE:
return "recvonly"
}
return "unknown"
}
type DTLSTransportState C.GstWebRTCDTLSTransportState
const (
DTLS_TRANSPORT_STATE_NEW DTLSTransportState = C.GST_WEBRTC_DTLS_TRANSPORT_STATE_NEW // new
DTLS_TRANSPORT_STATE_CLOSED DTLSTransportState = C.GST_WEBRTC_DTLS_TRANSPORT_STATE_CLOSED // closed
DTLS_TRANSPORT_STATE_FAILED DTLSTransportState = C.GST_WEBRTC_DTLS_TRANSPORT_STATE_FAILED // failed
DTLS_TRANSPORT_STATE_CONNECTING DTLSTransportState = C.GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTING // connecting
DTLS_TRANSPORT_STATE_CONNECTED DTLSTransportState = C.GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED // connected
)
func (e DTLSTransportState) String() string {
switch e {
case C.GST_WEBRTC_DTLS_TRANSPORT_STATE_NEW:
return "new"
case C.GST_WEBRTC_DTLS_TRANSPORT_STATE_CLOSED:
return "closed"
case C.GST_WEBRTC_DTLS_TRANSPORT_STATE_FAILED:
return "failed"
case C.GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTING:
return "connecting"
case C.GST_WEBRTC_DTLS_TRANSPORT_STATE_CONNECTED:
return "connected"
}
return "unknown"
}
type DataChannelState C.GstWebRTCDataChannelState
const (
DATA_CHANNEL_STATE_CONNECTING DataChannelState = C.GST_WEBRTC_DATA_CHANNEL_STATE_CONNECTING // connecting
DATA_CHANNEL_STATE_OPEN DataChannelState = C.GST_WEBRTC_DATA_CHANNEL_STATE_OPEN // open
DATA_CHANNEL_STATE_CLOSING DataChannelState = C.GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING // closing
DATA_CHANNEL_STATE_CLOSED DataChannelState = C.GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED // closed
)
func (e DataChannelState) String() string {
switch e {
case C.GST_WEBRTC_DATA_CHANNEL_STATE_CONNECTING:
return "connecting"
case C.GST_WEBRTC_DATA_CHANNEL_STATE_OPEN:
return "open"
case C.GST_WEBRTC_DATA_CHANNEL_STATE_CLOSING:
return "closing"
case C.GST_WEBRTC_DATA_CHANNEL_STATE_CLOSED:
return "closed"
}
return "unknown"
}
type Error C.GstWebRTCError
const (
ERROR_DATA_CHANNEL_FAILURE Error = C.GST_WEBRTC_ERROR_DATA_CHANNEL_FAILURE // data-channel-failure
ERROR_DTLS_FAILURE Error = C.GST_WEBRTC_ERROR_DTLS_FAILURE // dtls-failure
ERROR_FINGERPRINT_FAILURE Error = C.GST_WEBRTC_ERROR_FINGERPRINT_FAILURE // fingerprint-failure
ERROR_SCTP_FAILURE Error = C.GST_WEBRTC_ERROR_SCTP_FAILURE // sctp-failure
ERROR_SDP_SYNTAX_ERROR Error = C.GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR // sdp-syntax-error
ERROR_HARDWARE_ENCODER_NOT_AVAILABLE Error = C.GST_WEBRTC_ERROR_HARDWARE_ENCODER_NOT_AVAILABLE // hardware-encoder-not-available
ERROR_ENCODER_ERROR Error = C.GST_WEBRTC_ERROR_ENCODER_ERROR // encoder-error
ERROR_INVALID_STATE Error = C.GST_WEBRTC_ERROR_INVALID_STATE // invalid-state
ERROR_INTERNAL_FAILURE Error = C.GST_WEBRTC_ERROR_INTERNAL_FAILURE // internal-failure
ERROR_INVALID_MODIFICATION Error = C.GST_WEBRTC_ERROR_INVALID_MODIFICATION // invalid-modification
ERROR_TYPE_ERROR Error = C.GST_WEBRTC_ERROR_TYPE_ERROR // type-error
)
func (e Error) String() string {
switch e {
case C.GST_WEBRTC_ERROR_DATA_CHANNEL_FAILURE:
return "data-channel-failure"
case C.GST_WEBRTC_ERROR_DTLS_FAILURE:
return "dtls-failure"
case C.GST_WEBRTC_ERROR_FINGERPRINT_FAILURE:
return "fingerprint-failure"
case C.GST_WEBRTC_ERROR_SCTP_FAILURE:
return "sctp-failure"
case C.GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR:
return "sdp-syntax-error"
case C.GST_WEBRTC_ERROR_HARDWARE_ENCODER_NOT_AVAILABLE:
return "hardware-encoder-not-available"
case C.GST_WEBRTC_ERROR_ENCODER_ERROR:
return "encoder-error"
case C.GST_WEBRTC_ERROR_INVALID_STATE:
return "invalid-state"
case C.GST_WEBRTC_ERROR_INTERNAL_FAILURE:
return "internal-failure"
case C.GST_WEBRTC_ERROR_INVALID_MODIFICATION:
return "invalid-modification"
case C.GST_WEBRTC_ERROR_TYPE_ERROR:
return "type-error"
}
return "unknown"
}
type FECType C.GstWebRTCFECType
const (
FEC_TYPE_NONE FECType = C.GST_WEBRTC_FEC_TYPE_NONE // none
FEC_TYPE_ULP_RED FECType = C.GST_WEBRTC_FEC_TYPE_ULP_RED // ulpfec + red
)
func (e FECType) String() string {
switch e {
case C.GST_WEBRTC_FEC_TYPE_NONE:
return "none"
case C.GST_WEBRTC_FEC_TYPE_ULP_RED:
return "ulpfec + red"
}
return "unknown"
}
type ICEComponent C.GstWebRTCICEComponent
// GST_WEBRTC_ICE_COMPONENT_RTP (0)RTP component
// GST_WEBRTC_ICE_COMPONENT_RTCP (1)RTCP component
const (
ICE_COMPONENT_RTP ICEComponent = C.GST_WEBRTC_ICE_COMPONENT_RTP // RTP component
ICE_COMPONENT_RTCP ICEComponent = C.GST_WEBRTC_ICE_COMPONENT_RTCP // RTCP component
)
func (e ICEComponent) String() string {
switch e {
case C.GST_WEBRTC_ICE_COMPONENT_RTP:
return "RTP component"
case C.GST_WEBRTC_ICE_COMPONENT_RTCP:
return "RTCP component"
}
return "unknown"
}
type ICEConnectionState C.GstWebRTCICEConnectionState
const (
ICE_CONNECTION_STATE_NEW ICEConnectionState = C.GST_WEBRTC_ICE_CONNECTION_STATE_NEW // new
ICE_CONNECTION_STATE_CHECKING ICEConnectionState = C.GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING // checking
ICE_CONNECTION_STATE_CONNECTED ICEConnectionState = C.GST_WEBRTC_ICE_CONNECTION_STATE_CONNECTED // connected
ICE_CONNECTION_STATE_COMPLETED ICEConnectionState = C.GST_WEBRTC_ICE_CONNECTION_STATE_COMPLETED // completed
ICE_CONNECTION_STATE_FAILED ICEConnectionState = C.GST_WEBRTC_ICE_CONNECTION_STATE_FAILED // failed
ICE_CONNECTION_STATE_DISCONNECTED ICEConnectionState = C.GST_WEBRTC_ICE_CONNECTION_STATE_DISCONNECTED // disconnected
ICE_CONNECTION_STATE_CLOSED ICEConnectionState = C.GST_WEBRTC_ICE_CONNECTION_STATE_CLOSED // closed
)
func (e ICEConnectionState) String() string {
switch e {
case C.GST_WEBRTC_ICE_CONNECTION_STATE_NEW:
return "new"
case C.GST_WEBRTC_ICE_CONNECTION_STATE_CHECKING:
return "checking"
case C.GST_WEBRTC_ICE_CONNECTION_STATE_CONNECTED:
return "connected"
case C.GST_WEBRTC_ICE_CONNECTION_STATE_COMPLETED:
return "completed"
case C.GST_WEBRTC_ICE_CONNECTION_STATE_FAILED:
return "failed"
case C.GST_WEBRTC_ICE_CONNECTION_STATE_DISCONNECTED:
return "disconnected"
case C.GST_WEBRTC_ICE_CONNECTION_STATE_CLOSED:
return "closed"
}
return "unknown"
}
type ICEGatheringState C.GstWebRTCICEGatheringState
const (
ICE_GATHERING_STATE_NEW ICEGatheringState = C.GST_WEBRTC_ICE_GATHERING_STATE_NEW // new
ICE_GATHERING_STATE_GATHERING ICEGatheringState = C.GST_WEBRTC_ICE_GATHERING_STATE_GATHERING // gathering
ICE_GATHERING_STATE_COMPLETE ICEGatheringState = C.GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE // complete
)
func (e ICEGatheringState) String() string {
switch e {
case C.GST_WEBRTC_ICE_GATHERING_STATE_NEW:
return "new"
case C.GST_WEBRTC_ICE_GATHERING_STATE_GATHERING:
return "gathering"
case C.GST_WEBRTC_ICE_GATHERING_STATE_COMPLETE:
return "complete"
}
return "unknown"
}
type ICERole C.GstWebRTCICERole
const (
ICE_ROLE_CONTROLLED ICERole = C.GST_WEBRTC_ICE_ROLE_CONTROLLED // controlled
ICE_ROLE_CONTROLLING ICERole = C.GST_WEBRTC_ICE_ROLE_CONTROLLING // controlling
)
func (e ICERole) String() string {
switch e {
case C.GST_WEBRTC_ICE_ROLE_CONTROLLED:
return "controlled"
case C.GST_WEBRTC_ICE_ROLE_CONTROLLING:
return "controlling"
}
return "unknown"
}
type ICETransportPolicy C.GstWebRTCICETransportPolicy
const (
ICE_TRANSPORT_POLICY_ALL ICETransportPolicy = C.GST_WEBRTC_ICE_TRANSPORT_POLICY_ALL // all
ICE_TRANSPORT_POLICY_RELAY ICETransportPolicy = C.GST_WEBRTC_ICE_TRANSPORT_POLICY_RELAY // relay
)
func (e ICETransportPolicy) String() string {
switch e {
case C.GST_WEBRTC_ICE_TRANSPORT_POLICY_ALL:
return "all"
case C.GST_WEBRTC_ICE_TRANSPORT_POLICY_RELAY:
return "relay"
}
return "unknown"
}
type Kind C.GstWebRTCKind
const (
UNKNOWN Kind = C.GST_WEBRTC_KIND_UNKNOWN // unknown
AUDIO Kind = C.GST_WEBRTC_KIND_AUDIO // audio
VIDEO Kind = C.GST_WEBRTC_KIND_VIDEO // video
)
func (e Kind) String() string {
switch e {
case C.GST_WEBRTC_KIND_UNKNOWN:
return "unknown"
case C.GST_WEBRTC_KIND_AUDIO:
return "audio"
case C.GST_WEBRTC_KIND_VIDEO:
return "video"
}
return "unknown"
}
type PeerConnectionState C.GstWebRTCPeerConnectionState
// GST_WEBRTC_PEER_CONNECTION_STATE_NEW (0)new
// GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTING (1)connecting
// GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED (2)connected
// GST_WEBRTC_PEER_CONNECTION_STATE_DISCONNECTED (3)disconnected
// GST_WEBRTC_PEER_CONNECTION_STATE_FAILED (4)failed
// GST_WEBRTC_PEER_CONNECTION_STATE_CLOSED (5)closed
const (
PEER_CONNECTION_STATE_NEW PeerConnectionState = C.GST_WEBRTC_PEER_CONNECTION_STATE_NEW // new
PEER_CONNECTION_STATE_CONNECTING PeerConnectionState = C.GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTING // connecting
PEER_CONNECTION_STATE_CONNECTED PeerConnectionState = C.GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED // connected
PEER_CONNECTION_STATE_DISCONNECTED PeerConnectionState = C.GST_WEBRTC_PEER_CONNECTION_STATE_DISCONNECTED // disconnected
PEER_CONNECTION_STATE_FAILED PeerConnectionState = C.GST_WEBRTC_PEER_CONNECTION_STATE_FAILED // failed
PEER_CONNECTION_STATE_CLOSED PeerConnectionState = C.GST_WEBRTC_PEER_CONNECTION_STATE_CLOSED // closed
)
func (e PeerConnectionState) String() string {
switch e {
case C.GST_WEBRTC_PEER_CONNECTION_STATE_NEW:
return "new"
case C.GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTING:
return "connecting"
case C.GST_WEBRTC_PEER_CONNECTION_STATE_CONNECTED:
return "connected"
case C.GST_WEBRTC_PEER_CONNECTION_STATE_DISCONNECTED:
return "disconnected"
case C.GST_WEBRTC_PEER_CONNECTION_STATE_FAILED:
return "failed"
case C.GST_WEBRTC_PEER_CONNECTION_STATE_CLOSED:
return "closed"
}
return "unknown"
}
type PriorityType C.GstWebRTCPriorityType
const (
PRIORITY_TYPE_VERY_LOW PriorityType = C.GST_WEBRTC_PRIORITY_TYPE_VERY_LOW // very-low
PRIORITY_TYPE_LOW PriorityType = C.GST_WEBRTC_PRIORITY_TYPE_LOW // low
PRIORITY_TYPE_MEDIUM PriorityType = C.GST_WEBRTC_PRIORITY_TYPE_MEDIUM // medium
PRIORITY_TYPE_HIGH PriorityType = C.GST_WEBRTC_PRIORITY_TYPE_HIGH // high
)
func (e PriorityType) String() string {
switch e {
case C.GST_WEBRTC_PRIORITY_TYPE_VERY_LOW:
return "very-low"
case C.GST_WEBRTC_PRIORITY_TYPE_LOW:
return "low"
case C.GST_WEBRTC_PRIORITY_TYPE_MEDIUM:
return "medium"
case C.GST_WEBRTC_PRIORITY_TYPE_HIGH:
return "high"
}
return "unknown"
}
type RTPTransceiverDirection C.GstWebRTCRTPTransceiverDirection
const (
RTP_TRANSCEIVER_DIRECTION_NONE RTPTransceiverDirection = C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE // none
RTP_TRANSCEIVER_DIRECTION_INACTIVE RTPTransceiverDirection = C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE // inactive
RTP_TRANSCEIVER_DIRECTION_SENDONLY RTPTransceiverDirection = C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY // sendonly
RTP_TRANSCEIVER_DIRECTION_RECVONLY RTPTransceiverDirection = C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY // recvonly
RTP_TRANSCEIVER_DIRECTION_SENDRECV RTPTransceiverDirection = C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV // sendrecv
)
func (e RTPTransceiverDirection) String() string {
switch e {
case C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE:
return "none"
case C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE:
return "inactive"
case C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY:
return "sendonly"
case C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY:
return "recvonly"
case C.GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV:
return "sendrecv"
}
return "unknown"
}
type SCTPTransportState C.GstWebRTCSCTPTransportState
const (
SCTP_TRANSPORT_STATE_NEW SCTPTransportState = C.GST_WEBRTC_SCTP_TRANSPORT_STATE_NEW // new
SCTP_TRANSPORT_STATE_CONNECTING SCTPTransportState = C.GST_WEBRTC_SCTP_TRANSPORT_STATE_CONNECTING // connecting
SCTP_TRANSPORT_STATE_CONNECTED SCTPTransportState = C.GST_WEBRTC_SCTP_TRANSPORT_STATE_CONNECTED // connected
SCTP_TRANSPORT_STATE_CLOSED SCTPTransportState = C.GST_WEBRTC_SCTP_TRANSPORT_STATE_CLOSED // closed
)
func (e SCTPTransportState) String() string {
switch e {
case C.GST_WEBRTC_SCTP_TRANSPORT_STATE_NEW:
return "new"
case C.GST_WEBRTC_SCTP_TRANSPORT_STATE_CONNECTING:
return "connecting"
case C.GST_WEBRTC_SCTP_TRANSPORT_STATE_CONNECTED:
return "connected"
case C.GST_WEBRTC_SCTP_TRANSPORT_STATE_CLOSED:
return "closed"
}
return "unknown"
}
type SDPType C.GstWebRTCSDPType
const (
SDP_TYPE_OFFER SDPType = C.GST_WEBRTC_SDP_TYPE_OFFER // offer
SDP_TYPE_PRANSWER SDPType = C.GST_WEBRTC_SDP_TYPE_PRANSWER // pranswer
SDP_TYPE_ANSWER SDPType = C.GST_WEBRTC_SDP_TYPE_ANSWER // answer
SDP_TYPE_ROLLBACK SDPType = C.GST_WEBRTC_SDP_TYPE_ROLLBACK // rollback
)
func (e SDPType) String() string {
// returned string is const gchar* and must not be freed
cstring := C.gst_webrtc_sdp_type_to_string(C.GstWebRTCSDPType(e))
return C.GoString(cstring)
}
func SDPTypeFromString(s string) SDPType {
switch s {
case "offer":
return SDP_TYPE_OFFER
case "pranswer":
return SDP_TYPE_PRANSWER
case "answer":
return SDP_TYPE_ANSWER
case "rollback":
return SDP_TYPE_ROLLBACK
default:
panic("Unknown SDPType")
}
}
type SignalingState C.GstWebRTCSignalingState
const (
SIGNALING_STATE_STABLE SignalingState = C.GST_WEBRTC_SIGNALING_STATE_STABLE // stable
SIGNALING_STATE_CLOSED SignalingState = C.GST_WEBRTC_SIGNALING_STATE_CLOSED // closed
SIGNALING_STATE_HAVE_LOCAL_OFFER SignalingState = C.GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER // have-local-offer
SIGNALING_STATE_HAVE_REMOTE_OFFER SignalingState = C.GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER // have-remote-offer
SIGNALING_STATE_HAVE_LOCAL_PRANSWER SignalingState = C.GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER // have-local-pranswer
SIGNALING_STATE_HAVE_REMOTE_PRANSWER SignalingState = C.GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER // have-remote-pranswer
)
func (e SignalingState) String() string {
switch e {
case C.GST_WEBRTC_SIGNALING_STATE_STABLE:
return "stable"
case C.GST_WEBRTC_SIGNALING_STATE_CLOSED:
return "closed"
case C.GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_OFFER:
return "have-local-offer"
case C.GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_OFFER:
return "have-remote-offer"
case C.GST_WEBRTC_SIGNALING_STATE_HAVE_LOCAL_PRANSWER:
return "have-local-pranswer"
case C.GST_WEBRTC_SIGNALING_STATE_HAVE_REMOTE_PRANSWER:
return "have-remote-pranswer"
}
return "unknown"
}
type StatsType C.GstWebRTCStatsType
const (
STATS_CODEC StatsType = C.GST_WEBRTC_STATS_CODEC // codec
STATS_INBOUND_RTP StatsType = C.GST_WEBRTC_STATS_INBOUND_RTP // inbound-rtp
STATS_OUTBOUND_RTP StatsType = C.GST_WEBRTC_STATS_OUTBOUND_RTP // outbound-rtp
STATS_REMOTE_INBOUND_RTP StatsType = C.GST_WEBRTC_STATS_REMOTE_INBOUND_RTP // remote-inbound-rtp
STATS_REMOTE_OUTBOUND_RTP StatsType = C.GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP // remote-outbound-rtp
STATS_CSRC StatsType = C.GST_WEBRTC_STATS_CSRC // csrc
STATS_PEER_CONNECTION StatsType = C.GST_WEBRTC_STATS_PEER_CONNECTION // peer-connection
STATS_DATA_CHANNEL StatsType = C.GST_WEBRTC_STATS_DATA_CHANNEL // data-channel
STATS_STREAM StatsType = C.GST_WEBRTC_STATS_STREAM // stream
STATS_TRANSPORT StatsType = C.GST_WEBRTC_STATS_TRANSPORT // transport
STATS_CANDIDATE_PAIR StatsType = C.GST_WEBRTC_STATS_CANDIDATE_PAIR // candidate-pair
STATS_LOCAL_CANDIDATE StatsType = C.GST_WEBRTC_STATS_LOCAL_CANDIDATE // local-candidate
STATS_REMOTE_CANDIDATE StatsType = C.GST_WEBRTC_STATS_REMOTE_CANDIDATE // remote-candidate
STATS_CERTIFICATE StatsType = C.GST_WEBRTC_STATS_CERTIFICATE // certificate
)
func (e StatsType) String() string {
switch e {
case C.GST_WEBRTC_STATS_CODEC:
return "codec"
case C.GST_WEBRTC_STATS_INBOUND_RTP:
return "inbound-rtp"
case C.GST_WEBRTC_STATS_OUTBOUND_RTP:
return "outbound-rtp"
case C.GST_WEBRTC_STATS_REMOTE_INBOUND_RTP:
return "remote-inbound-rtp"
case C.GST_WEBRTC_STATS_REMOTE_OUTBOUND_RTP:
return "remote-outbound-rtp"
case C.GST_WEBRTC_STATS_CSRC:
return "csrc"
case C.GST_WEBRTC_STATS_PEER_CONNECTION:
return "peer-connection"
case C.GST_WEBRTC_STATS_DATA_CHANNEL:
return "data-channel"
case C.GST_WEBRTC_STATS_STREAM:
return "stream"
case C.GST_WEBRTC_STATS_TRANSPORT:
return "transport"
case C.GST_WEBRTC_STATS_CANDIDATE_PAIR:
return "candidate-pair"
case C.GST_WEBRTC_STATS_LOCAL_CANDIDATE:
return "local-candidate"
case C.GST_WEBRTC_STATS_REMOTE_CANDIDATE:
return "remote-candidate"
case C.GST_WEBRTC_STATS_CERTIFICATE:
return "certificate"
}
return "unknown"
}

8
gst/gstwebrtc/gst.go.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __GST_WEBRTC_GO_H__
#define __GST_WEBRTC_GO_H__
#define GST_USE_UNSTABLE_API // webrtc is unstable
#include <gst/webrtc/webrtc.h>
#endif

View File

@@ -0,0 +1,7 @@
package gstwebrtc
/*
#cgo pkg-config: gstreamer-webrtc-1.0
#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall
*/
import "C"

View File

@@ -0,0 +1,45 @@
package gstwebrtc
// #include "gst.go.h"
import "C"
import (
"unsafe"
"github.com/go-gst/go-glib/glib"
"github.com/go-gst/go-gst/gst"
)
func init() {
tm := []glib.TypeMarshaler{
{T: glib.Type(C.GST_TYPE_WEBRTC_RTP_TRANSCEIVER), F: marshalRTPTransceiver},
}
glib.RegisterGValueMarshalers(tm)
}
type RTPTransceiver struct {
*gst.Object
}
// ToGValue implements glib.ValueTransformer
func (tc *RTPTransceiver) ToGValue() (*glib.Value, error) {
val, err := glib.ValueInit(glib.Type(C.GST_TYPE_WEBRTC_RTP_TRANSCEIVER))
if err != nil {
return nil, err
}
val.SetInstance(unsafe.Pointer(tc.Instance()))
return val, nil
}
func wrapRTPTransceiver(p unsafe.Pointer) *RTPTransceiver {
return &RTPTransceiver{
Object: gst.FromGstObjectUnsafeNone(p),
}
}
func marshalRTPTransceiver(p unsafe.Pointer) (interface{}, error) {
c := C.g_value_get_object((*C.GValue)(p))
return wrapRTPTransceiver(unsafe.Pointer(c)), nil
}

View File

@@ -0,0 +1,9 @@
package gstwebrtc_test
import (
"testing"
)
func TestRTPTransceiverGValueMarshal(t *testing.T) {
t.Skip("Not implemented, because we don't have a constructor for RTPTransceiver")
}

View File

@@ -0,0 +1,120 @@
package gstwebrtc
// #include "gst.go.h"
import "C"
import (
"runtime"
"unsafe"
"github.com/go-gst/go-glib/glib"
"github.com/go-gst/go-gst/gst/gstsdp"
)
func init() {
tm := []glib.TypeMarshaler{
{T: glib.Type(C.GST_TYPE_WEBRTC_SESSION_DESCRIPTION), F: marshalSessionDescription},
}
glib.RegisterGValueMarshalers(tm)
}
type SessionDescription struct {
ptr *C.GstWebRTCSessionDescription
}
func NewSessionDescription(t SDPType, sdp *gstsdp.Message) *SessionDescription {
sd := C.gst_webrtc_session_description_new(
C.GstWebRTCSDPType(t),
(*C.GstSDPMessage)(sdp.UnownedCopy().Instance()),
)
return wrapSessionDescriptionAndFinalize(sd)
}
func wrapSessionDescriptionAndFinalize(sdp *C.GstWebRTCSessionDescription) *SessionDescription {
sd := &SessionDescription{
ptr: sdp,
}
// this requires that we copy the SDP message before passing it to any transfer-ownership function
runtime.SetFinalizer(sd, func(sd *SessionDescription) {
sd.Free()
})
return sd
}
// W3RTCSessionDescription is used to marshal/unmarshal SessionDescription to/from JSON.
//
// We cannot implement the json.(Un-)Marshaler interfaces on SessionDescription directly because
// the finalizer would run and free the memory, because the value would have to be copied.
//
// it complies with the WebRTC spec for SessionDescription, see https://www.w3.org/TR/webrtc/#rtcsessiondescription-class
type W3RTCSessionDescription struct {
Type string `json:"type"`
Sdp string `json:"sdp"`
}
// ToGstSDP converts a W3RTCSessionDescription to a SessionDescription
func (w3SDP *W3RTCSessionDescription) ToGstSDP() (*SessionDescription, error) {
sdp, err := gstsdp.ParseSDPMessage(w3SDP.Sdp)
if err != nil {
return nil, err
}
return NewSessionDescription(SDPTypeFromString(w3SDP.Type), sdp), nil
}
// ToW3SDP returns a W3RTCSessionDescription that can be marshaled to JSON
func (sd *SessionDescription) ToW3SDP() W3RTCSessionDescription {
jsonSDP := W3RTCSessionDescription{
Type: SDPType(sd.ptr._type).String(),
Sdp: gstsdp.NewMessageFromUnsafe(unsafe.Pointer(sd.ptr.sdp)).String(),
}
return jsonSDP
}
func (sd *SessionDescription) Free() {
C.gst_webrtc_session_description_free(sd.ptr)
}
// UnownedCopy creates a new copy of the SessionDescription that will not be finalized
//
// this is needed for passing the SessionDescription to other functions that will take ownership of it.
//
// used in the bindings, should not be called by application code
func (sd *SessionDescription) UnownedCopy() *SessionDescription {
newSD := C.gst_webrtc_session_description_copy(sd.ptr)
return &SessionDescription{
ptr: newSD,
}
}
// Copy creates a new copy of the SessionDescription
func (sd *SessionDescription) Copy() *SessionDescription {
return wrapSessionDescriptionAndFinalize(sd.UnownedCopy().ptr)
}
// ToGValue implements glib.ValueTransformer
func (sd *SessionDescription) ToGValue() (*glib.Value, error) {
val, err := glib.ValueInit(glib.Type(C.GST_TYPE_WEBRTC_SESSION_DESCRIPTION))
if err != nil {
return nil, err
}
val.SetBoxed(unsafe.Pointer(sd.ptr))
return val, nil
}
func marshalSessionDescription(p unsafe.Pointer) (interface{}, error) {
c := C.g_value_get_boxed((*C.GValue)(p))
// we don't own this memory, so we need to copy it to prevent other code from freeing it
ref := &SessionDescription{
ptr: (*C.GstWebRTCSessionDescription)(c),
}
return ref.Copy(), nil
}

View File

@@ -0,0 +1,58 @@
package gstwebrtc_test
import (
"testing"
"github.com/go-gst/go-gst/gst/gstsdp"
"github.com/go-gst/go-gst/gst/gstwebrtc"
)
func TestSessionDescriptionGValueMarshal(t *testing.T) {
sdp, err := gstsdp.ParseSDPMessage("v=0\nm=audio 4000 RTP/AVP 111\na=rtpmap:111 OPUS/48000/2\nm=video 4000 RTP/AVP 96\na=rtpmap:96 VP8/90000\na=my-sdp-value")
if err != nil {
t.Fatal(err)
}
sd := gstwebrtc.NewSessionDescription(gstwebrtc.SDP_TYPE_OFFER, sdp)
gv, err := sd.ToGValue()
if err != nil {
t.Fatal(err)
}
sdI, err := gv.GoValue()
if err != nil {
t.Fatal(err)
}
sd, ok := sdI.(*gstwebrtc.SessionDescription)
if !ok {
t.Fatal("Failed to convert to SessionDescription")
}
_ = sd
}
func TestSessionDescriptionJSONMarshal(t *testing.T) {
sdp, err := gstsdp.ParseSDPMessage("v=0\nm=audio 4000 RTP/AVP 111\na=rtpmap:111 OPUS/48000/2\nm=video 4000 RTP/AVP 96\na=rtpmap:96 VP8/90000\na=my-sdp-value")
if err != nil {
t.Fatal(err)
}
sd := gstwebrtc.NewSessionDescription(gstwebrtc.SDP_TYPE_OFFER, sdp)
w3 := sd.ToW3SDP()
sd, err = w3.ToGstSDP()
if err != nil {
t.Fatal(err)
}
_ = sd
}