diff --git a/go.mod b/go.mod index 8930007..802331c 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index cc1349b..b71e562 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/gst/gst_promise.go b/gst/gst_promise.go index 20ed8a9..a9d30be 100644 --- a/gst/gst_promise.go +++ b/gst/gst_promise.go @@ -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 } diff --git a/gst/gst_promise_test.go b/gst/gst_promise_test.go index 3d29f42..bc3baa8 100644 --- a/gst/gst_promise_test.go +++ b/gst/gst_promise_test.go @@ -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) diff --git a/gst/gstsdp/doc.go b/gst/gstsdp/doc.go new file mode 100644 index 0000000..55afb74 --- /dev/null +++ b/gst/gstsdp/doc.go @@ -0,0 +1,2 @@ +// gstsdp contains bindings for the gstreamer sdp library. See also https://gstreamer.freedesktop.org/documentation/sdp/index.html +package gstsdp diff --git a/gst/gstsdp/gst.go.h b/gst/gstsdp/gst.go.h new file mode 100644 index 0000000..1a7f385 --- /dev/null +++ b/gst/gstsdp/gst.go.h @@ -0,0 +1,6 @@ +#ifndef __GST_WEBRTC_GO_H__ +#define __GST_WEBRTC_GO_H__ + +#include + +#endif diff --git a/gst/gstsdp/message.go b/gst/gstsdp/message.go new file mode 100644 index 0000000..ec176a3 --- /dev/null +++ b/gst/gstsdp/message.go @@ -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) +} diff --git a/gst/gstsdp/pkg_config.go b/gst/gstsdp/pkg_config.go new file mode 100644 index 0000000..3226db9 --- /dev/null +++ b/gst/gstsdp/pkg_config.go @@ -0,0 +1,7 @@ +package gstsdp + +/* +#cgo pkg-config: gstreamer-sdp-1.0 +#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall +*/ +import "C" diff --git a/gst/gstwebrtc/data_channel.go b/gst/gstwebrtc/data_channel.go new file mode 100644 index 0000000..1d496cf --- /dev/null +++ b/gst/gstwebrtc/data_channel.go @@ -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 +} diff --git a/gst/gstwebrtc/data_channel_test.go b/gst/gstwebrtc/data_channel_test.go new file mode 100644 index 0000000..2035f2c --- /dev/null +++ b/gst/gstwebrtc/data_channel_test.go @@ -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 +} diff --git a/gst/gstwebrtc/doc.go b/gst/gstwebrtc/doc.go new file mode 100644 index 0000000..cecdaeb --- /dev/null +++ b/gst/gstwebrtc/doc.go @@ -0,0 +1,2 @@ +// gstwebrtc contains bindings for the gstreamer webrtclib. See also https://gstreamer.freedesktop.org/documentation/webrtclib/index.html +package gstwebrtc diff --git a/gst/gstwebrtc/enums.go b/gst/gstwebrtc/enums.go new file mode 100644 index 0000000..b463106 --- /dev/null +++ b/gst/gstwebrtc/enums.go @@ -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" +} diff --git a/gst/gstwebrtc/gst.go.h b/gst/gstwebrtc/gst.go.h new file mode 100644 index 0000000..913332c --- /dev/null +++ b/gst/gstwebrtc/gst.go.h @@ -0,0 +1,8 @@ +#ifndef __GST_WEBRTC_GO_H__ +#define __GST_WEBRTC_GO_H__ + +#define GST_USE_UNSTABLE_API // webrtc is unstable + +#include + +#endif diff --git a/gst/gstwebrtc/pkg_config.go b/gst/gstwebrtc/pkg_config.go new file mode 100644 index 0000000..c524279 --- /dev/null +++ b/gst/gstwebrtc/pkg_config.go @@ -0,0 +1,7 @@ +package gstwebrtc + +/* +#cgo pkg-config: gstreamer-webrtc-1.0 +#cgo CFLAGS: -Wno-deprecated-declarations -g -Wall +*/ +import "C" diff --git a/gst/gstwebrtc/rtp_transceiver.go b/gst/gstwebrtc/rtp_transceiver.go new file mode 100644 index 0000000..34dabc4 --- /dev/null +++ b/gst/gstwebrtc/rtp_transceiver.go @@ -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 +} diff --git a/gst/gstwebrtc/rtp_transceiver_test.go b/gst/gstwebrtc/rtp_transceiver_test.go new file mode 100644 index 0000000..0db8142 --- /dev/null +++ b/gst/gstwebrtc/rtp_transceiver_test.go @@ -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") +} diff --git a/gst/gstwebrtc/session_description.go b/gst/gstwebrtc/session_description.go new file mode 100644 index 0000000..7c95966 --- /dev/null +++ b/gst/gstwebrtc/session_description.go @@ -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 +} diff --git a/gst/gstwebrtc/session_description_test.go b/gst/gstwebrtc/session_description_test.go new file mode 100644 index 0000000..8092294 --- /dev/null +++ b/gst/gstwebrtc/session_description_test.go @@ -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 +}