From 22f476baa83dba1610822e0506448b9923cc8766 Mon Sep 17 00:00:00 2001 From: Jan Weber Date: Fri, 16 Jun 2023 19:07:26 +0200 Subject: [PATCH 1/4] Avoid garbage collection of promise for createOffer/createAnswer --- src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java b/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java index c3198102..b292e0b6 100644 --- a/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java +++ b/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java @@ -43,6 +43,8 @@ public class WebRTCBin extends Bin { public static final String GST_NAME = "webrtcbin"; public static final String GTYPE_NAME = "GstWebRTCBin"; + private Promise createOfferPromise; + private Promise createAnswerPromise; WebRTCBin(Initializer init) { super(init); @@ -138,7 +140,7 @@ public void callback(Element elem, int sdpMLineIndex, String candidate) { * @param listener callback that is called when a offer is created */ public void createOffer(final CREATE_OFFER listener) { - Promise promise = new Promise(new Promise.PROMISE_CHANGE() { + createOfferPromise = new Promise(new Promise.PROMISE_CHANGE() { @SuppressWarnings("unused") public void onChange(Promise promise) { Structure reply = promise.getReply(); @@ -147,7 +149,7 @@ public void onChange(Promise promise) { promise.dispose(); } }); - emit("create-offer", null, promise); + emit("create-offer", null, createOfferPromise); } /** @@ -162,7 +164,7 @@ public void onChange(Promise promise) { * @param listener callback that is called when an answer is created. */ public void createAnswer(final CREATE_ANSWER listener) { - Promise promise = new Promise(new Promise.PROMISE_CHANGE() { + createAnswerPromise = new Promise(new Promise.PROMISE_CHANGE() { @SuppressWarnings("unused") public void onChange(Promise promise) { Structure reply = promise.getReply(); @@ -171,7 +173,7 @@ public void onChange(Promise promise) { promise.dispose(); } }); - emit("create-answer", null, promise); + emit("create-answer", null, createAnswerPromise); } /** From 63dbb2de3096ae08afcfb10caf40cb154df94d18 Mon Sep 17 00:00:00 2001 From: Jan Weber Date: Fri, 16 Jun 2023 21:34:42 +0200 Subject: [PATCH 2/4] Avoid garbage collection of promise for createOffer/createAnswer --- src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java b/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java index b292e0b6..141989de 100644 --- a/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java +++ b/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java @@ -140,7 +140,7 @@ public void callback(Element elem, int sdpMLineIndex, String candidate) { * @param listener callback that is called when a offer is created */ public void createOffer(final CREATE_OFFER listener) { - createOfferPromise = new Promise(new Promise.PROMISE_CHANGE() { + Promise promise = new Promise(new Promise.PROMISE_CHANGE() { @SuppressWarnings("unused") public void onChange(Promise promise) { Structure reply = promise.getReply(); @@ -149,7 +149,8 @@ public void onChange(Promise promise) { promise.dispose(); } }); - emit("create-offer", null, createOfferPromise); + emit("create-offer", null, promise); + createOfferPromise = promise; } /** @@ -164,7 +165,7 @@ public void onChange(Promise promise) { * @param listener callback that is called when an answer is created. */ public void createAnswer(final CREATE_ANSWER listener) { - createAnswerPromise = new Promise(new Promise.PROMISE_CHANGE() { + Promise promise = new Promise(new Promise.PROMISE_CHANGE() { @SuppressWarnings("unused") public void onChange(Promise promise) { Structure reply = promise.getReply(); @@ -173,7 +174,8 @@ public void onChange(Promise promise) { promise.dispose(); } }); - emit("create-answer", null, createAnswerPromise); + emit("create-answer", null, promise); + createAnswerPromise = promise; } /** From d73b8b8b4c47dce1f13c505d0d8accf9dcdb35e6 Mon Sep 17 00:00:00 2001 From: Jan Weber Date: Sat, 8 Jul 2023 10:20:10 +0200 Subject: [PATCH 3/4] Support for DataChannel (string/data) --- .../freedesktop/gstreamer/glib/GBytes.java | 66 +++++++++ src/org/freedesktop/gstreamer/glib/GLib.java | 1 + .../gstreamer/lowlevel/GlibAPI.java | 8 + .../freedesktop/gstreamer/webrtc/WebRTC.java | 3 + .../gstreamer/webrtc/WebRTCBin.java | 55 +++++-- .../gstreamer/webrtc/WebRTCDataChannel.java | 137 ++++++++++++++++++ 6 files changed, 261 insertions(+), 9 deletions(-) create mode 100644 src/org/freedesktop/gstreamer/glib/GBytes.java create mode 100644 src/org/freedesktop/gstreamer/webrtc/WebRTCDataChannel.java diff --git a/src/org/freedesktop/gstreamer/glib/GBytes.java b/src/org/freedesktop/gstreamer/glib/GBytes.java new file mode 100644 index 00000000..87eab101 --- /dev/null +++ b/src/org/freedesktop/gstreamer/glib/GBytes.java @@ -0,0 +1,66 @@ +package org.freedesktop.gstreamer.glib; + +import com.sun.jna.Memory; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.IntByReference; +import org.freedesktop.gstreamer.lowlevel.GPointer; + +import static org.freedesktop.gstreamer.lowlevel.GlibAPI.GLIB_API; + +/** + * Wrapper to the GBytes data structure, + * see https://docs.gtk.org/glib/struct.Bytes.html + */ +public class GBytes extends RefCountedObject { + + public static final String GTYPE_NAME = "GBytes"; + + GBytes(Initializer init) { + super(new GBytes.Handle(init.ptr, init.ownsHandle), init.needRef); + } + + GBytes(Handle handle) { + super(handle); + } + + public int getSize() { + return GLIB_API.g_bytes_get_size(getRawPointer()); + } + + public byte[] getBytes() { + IntByReference size = new IntByReference(); + Pointer bytes = GLIB_API.g_bytes_get_data(getRawPointer(), size); + return bytes != null ? bytes.getByteArray(0, size.getValue()) : new byte[0]; + } + + public static GBytes createInstance(byte[] bytes) { + Pointer source = new Memory(bytes.length); + source.write(0, bytes, 0, bytes.length); + Pointer ptr = GLIB_API.g_bytes_new(source, bytes.length); + return new GBytes(new Handle(new GPointer(ptr), true)); + } + + private static final class Handle extends RefCountedObject.Handle { + + Handle(GPointer ptr, boolean ownsHandle) { + super(ptr, ownsHandle); + } + + @Override + protected void disposeNativeHandle(GPointer ptr) { + GLIB_API.g_bytes_unref(ptr.getPointer()); + } + + @Override + protected void ref() { + GLIB_API.g_bytes_ref(getPointer().getPointer()); + } + + @Override + protected void unref() { + GLIB_API.g_bytes_unref(getPointer().getPointer()); + } + + } + +} diff --git a/src/org/freedesktop/gstreamer/glib/GLib.java b/src/org/freedesktop/gstreamer/glib/GLib.java index a123ceac..366ad0c7 100644 --- a/src/org/freedesktop/gstreamer/glib/GLib.java +++ b/src/org/freedesktop/gstreamer/glib/GLib.java @@ -97,6 +97,7 @@ public static class Types implements NativeObject.TypeProvider { @Override public Stream> types() { return Stream.of( + registration(GBytes.class, GBytes.GTYPE_NAME, GBytes::new), registration(GDate.class, GDate.GTYPE_NAME, GDate::new), registration(GInetAddress.class, GInetAddress.GTYPE_NAME, GInetAddress::new), registration(GSocket.class, GSocket.GTYPE_NAME, GSocket::new), diff --git a/src/org/freedesktop/gstreamer/lowlevel/GlibAPI.java b/src/org/freedesktop/gstreamer/lowlevel/GlibAPI.java index a9239329..6e56ed92 100644 --- a/src/org/freedesktop/gstreamer/lowlevel/GlibAPI.java +++ b/src/org/freedesktop/gstreamer/lowlevel/GlibAPI.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; +import com.sun.jna.ptr.IntByReference; import org.freedesktop.gstreamer.lowlevel.annotations.CallerOwnsReturn; import com.sun.jna.Callback; @@ -122,6 +123,13 @@ int g_timeout_add_full(int priority, int interval, GSourceFunc function, int g_date_get_day(Pointer date); void g_date_free(Pointer date); + Pointer g_bytes_new(Pointer source, int size); + Pointer g_bytes_get_data(Pointer bytes, IntByReference size); + int g_bytes_get_size(Pointer bytes); + Pointer g_bytes_ref(Pointer bytes); + void g_bytes_unref(Pointer bytes); + + GList g_list_append(GList list, Pointer data); public static final class GList extends com.sun.jna.Structure { diff --git a/src/org/freedesktop/gstreamer/webrtc/WebRTC.java b/src/org/freedesktop/gstreamer/webrtc/WebRTC.java index 0195e9a6..b31c03fc 100644 --- a/src/org/freedesktop/gstreamer/webrtc/WebRTC.java +++ b/src/org/freedesktop/gstreamer/webrtc/WebRTC.java @@ -35,6 +35,9 @@ public static class Types implements NativeObject.TypeProvider { @Override public Stream> types() { return Stream.of( + registration(WebRTCDataChannel.class, + WebRTCDataChannel.GTYPE_NAME, + WebRTCDataChannel::new), registration(WebRTCSessionDescription.class, WebRTCSessionDescription.GTYPE_NAME, WebRTCSessionDescription::new), diff --git a/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java b/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java index 141989de..b1f8fecb 100644 --- a/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java +++ b/src/org/freedesktop/gstreamer/webrtc/WebRTCBin.java @@ -18,13 +18,12 @@ */ package org.freedesktop.gstreamer.webrtc; -import org.freedesktop.gstreamer.Bin; -import org.freedesktop.gstreamer.Element; -import org.freedesktop.gstreamer.Gst; -import org.freedesktop.gstreamer.Promise; -import org.freedesktop.gstreamer.Structure; +import com.sun.jna.ptr.PointerByReference; +import org.freedesktop.gstreamer.*; import org.freedesktop.gstreamer.glib.NativeEnum; +import org.freedesktop.gstreamer.glib.Natives; +import org.freedesktop.gstreamer.lowlevel.GstAPI; import org.freedesktop.gstreamer.lowlevel.GstAPI.GstCallback; /** @@ -43,8 +42,8 @@ public class WebRTCBin extends Bin { public static final String GST_NAME = "webrtcbin"; public static final String GTYPE_NAME = "GstWebRTCBin"; - private Promise createOfferPromise; - private Promise createAnswerPromise; + private Promise promiseCreateOffer; + private Promise promiseCreateAnswer; WebRTCBin(Initializer init) { super(init); @@ -150,7 +149,7 @@ public void onChange(Promise promise) { } }); emit("create-offer", null, promise); - createOfferPromise = promise; + promiseCreateOffer = promise; } /** @@ -175,7 +174,7 @@ public void onChange(Promise promise) { } }); emit("create-answer", null, promise); - createAnswerPromise = promise; + promiseCreateAnswer = promise; } /** @@ -300,4 +299,42 @@ public WebRTCSessionDescription getRemoteDescription() { description.disown(); return description; } + + /** + * Creates a {@link WebRTCDataChannel} that can be used to send messages to other connected peers + *

+ * Should be called once this {@link WebRTCBin} is in the READY state. Otherwise an error will be thrown + *

+ * @return the new {@link WebRTCDataChannel} + */ + public WebRTCDataChannel createDataChannel(String label) { + if (getState() != State.READY) { + throw new IllegalStateException("WebRTCBin must be in state READY"); + } + PointerByReference channel = new PointerByReference(); + emit("create-data-channel", label, null, channel); + return Natives.objectFor(channel.getValue(), WebRTCDataChannel.class, false, true); + } + + /** + * Signal emitted when this {@link WebRTCBin} receives a {@link WebRTCDataChannel} from + * a remote peer. + */ + public interface ON_DATA_CHANNEL { + void onDataChannel(WebRTCDataChannel channel); + } + + /** + * Adds a listener for the on-data-channel signal. + * + * @param listener + */ + public void connect(final ON_DATA_CHANNEL listener) { + connect(ON_DATA_CHANNEL.class, listener, new GstAPI.GstCallback() { + @SuppressWarnings("unused") + public void callback(Element elem, WebRTCDataChannel channel) { + listener.onDataChannel(channel); + } + }); + } } diff --git a/src/org/freedesktop/gstreamer/webrtc/WebRTCDataChannel.java b/src/org/freedesktop/gstreamer/webrtc/WebRTCDataChannel.java new file mode 100644 index 00000000..c378e0c8 --- /dev/null +++ b/src/org/freedesktop/gstreamer/webrtc/WebRTCDataChannel.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018 Antonio Morales + * + * This file is part of gstreamer-java. + * + * This code is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License version 3 only, as published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 along with + * this work. If not, see . + */ + +package org.freedesktop.gstreamer.webrtc; + +import org.freedesktop.gstreamer.Gst; +import org.freedesktop.gstreamer.GstObject; +import org.freedesktop.gstreamer.glib.GBytes; +import org.freedesktop.gstreamer.lowlevel.GstAPI.GstCallback; + +/** + * + * @see https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/blob/master/ext/webrtc/webrtcdatachannel.h + * available since Gstreamer 1.15 + */ +@Gst.Since(minor = 15) +public class WebRTCDataChannel extends GstObject { + public static final String GTYPE_NAME = "GstWebRTCDataChannel"; + + public WebRTCDataChannel(Initializer init) { + super(init); + } + + /** + * Signal emitted when this {@link WebRTCDataChannel} has an error. + */ + public static interface ON_ERROR { + public void onError(WebRTCDataChannel channel); + } + + /** + * Signal emitted when this {@link WebRTCDataChannel} is opened. + */ + public static interface ON_OPEN { + public void onOpen(WebRTCDataChannel channel); + } + + /** + * Signal emitted when this {@link WebRTCDataChannel} is closed. + */ + public static interface ON_CLOSE { + public void onClose(WebRTCDataChannel channel); + } + + /** + * Signal emitted when this {@link WebRTCDataChannel} receives a string message from a remote peer. + */ + public static interface ON_MESSAGE_STRING { + public void onMessage(WebRTCDataChannel channel, String message); + } + + public void connect(final ON_ERROR listener) { + connect(ON_ERROR.class, listener, new GstCallback() { + @SuppressWarnings("unused") + public void callback(GstObject channel) { + listener.onError((WebRTCDataChannel) channel); + } + }); + } + + public void connect(final ON_OPEN listener) { + connect(ON_OPEN.class, listener, new GstCallback() { + @SuppressWarnings("unused") + public void callback(GstObject channel) { + listener.onOpen((WebRTCDataChannel) channel); + } + }); + } + + public void connect(final ON_CLOSE listener) { + connect(ON_CLOSE.class, listener, new GstCallback() { + @SuppressWarnings("unused") + public void callback(GstObject channel) { + listener.onClose((WebRTCDataChannel) channel); + } + }); + } + + public void connect(final ON_MESSAGE_STRING listener) { + connect(ON_MESSAGE_STRING.class, listener, new GstCallback() { + @SuppressWarnings("unused") + public void callback(GstObject channel, String message) { + listener.onMessage((WebRTCDataChannel) channel, message); + } + }); + } + + /** + * Sends a string through this {@link WebRTCDataChannel} which will be received by connected remote peers. + * @param message that should be sent over the WebRTC data-channel connection. + */ + public void sendMessage(String message) { + emit("send-string", message); + } + + + /** + * Signal emitted when this {@link WebRTCDataChannel} receives binary data from a remote peer. + */ + public interface ON_MESSAGE_DATA { + void onMessage(WebRTCDataChannel channel, byte[] data); + } + + public void connect(final ON_MESSAGE_DATA listener) { + connect(ON_MESSAGE_DATA.class, listener, new GstCallback() { + @SuppressWarnings("unused") + public void callback(GstObject channel, GBytes gbytes) { + listener.onMessage((WebRTCDataChannel) channel, gbytes.getBytes()); + gbytes.dispose(); + } + }); + } + + /** + * Sends binary data through this {@link WebRTCDataChannel} which will be received by connected remote peers. + * @param bytes that should be sent over the WebRTC data-channel connection. + */ + public void sendMessage(byte[] bytes) { + GBytes gbytes = GBytes.createInstance(bytes); + gbytes.disown(); + emit("send-data", gbytes); + } + +} \ No newline at end of file From 0910e25d34afade4965a1ecdb53a8d7be642645f Mon Sep 17 00:00:00 2001 From: Jan Weber Date: Sat, 8 Jul 2023 14:10:49 +0200 Subject: [PATCH 4/4] Added missing copyright/license text --- src/org/freedesktop/gstreamer/glib/GBytes.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/org/freedesktop/gstreamer/glib/GBytes.java b/src/org/freedesktop/gstreamer/glib/GBytes.java index 87eab101..c5805988 100644 --- a/src/org/freedesktop/gstreamer/glib/GBytes.java +++ b/src/org/freedesktop/gstreamer/glib/GBytes.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2023 Jan Weber + * + * This file is part of gstreamer-java. + * + * This code is free software: you can redistribute it and/or modify it under the terms of the GNU + * Lesser General Public License version 3 only, as published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 along with + * this work. If not, see . + */ package org.freedesktop.gstreamer.glib; import com.sun.jna.Memory;