Skip to content

Commit 5354281

Browse files
Update and document FXImageSink before releasing.
- make image property read-only externally. - add method to clear image. - add methods to control frame dimensions and frame rate on AppSink caps. - add JavaDoc.
1 parent dcf808e commit 5354281

File tree

1 file changed

+143
-27
lines changed

1 file changed

+143
-27
lines changed

src/main/java/org/freedesktop/gstreamer/fx/FXImageSink.java

Lines changed: 143 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
import java.nio.ByteBuffer;
2323
import java.nio.ByteOrder;
2424
import javafx.application.Platform;
25-
import javafx.beans.property.ObjectProperty;
26-
import javafx.beans.property.SimpleObjectProperty;
25+
import javafx.beans.property.ReadOnlyObjectProperty;
26+
import javafx.beans.property.ReadOnlyObjectWrapper;
2727
import javafx.scene.image.Image;
2828
import javafx.scene.image.PixelBuffer;
2929
import javafx.scene.image.PixelFormat;
@@ -36,12 +36,18 @@
3636
import org.freedesktop.gstreamer.elements.AppSink;
3737

3838
/**
39-
*
39+
* A wrapper connecting a GStreamer AppSink and a JavaFX Image, making use of
40+
* {@link PixelBuffer} to directly access the native GStreamer pixel data.
41+
* <p>
42+
* Use {@link #imageProperty()} to access the JavaFX image. The Image should
43+
* only be used on the JavaFX application thread, and is only valid while it is
44+
* the current property value. Using the Image when it is no longer the current
45+
* property value may cause errors or crashes.
4046
*/
4147
public class FXImageSink {
42-
48+
4349
private final static String DEFAULT_CAPS;
44-
50+
4551
static {
4652
if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
4753
DEFAULT_CAPS = "video/x-raw, format=BGRx";
@@ -51,18 +57,31 @@ public class FXImageSink {
5157
}
5258

5359
private final AppSink sink;
54-
private final ObjectProperty<Image> image;
60+
private final ReadOnlyObjectWrapper<Image> image;
5561
private final AtomicReference<Sample> pending;
5662
private final NewSampleListener newSampleListener;
5763
private final NewPrerollListener newPrerollListener;
58-
64+
5965
private Sample activeSample;
6066
private Buffer activeBuffer;
61-
67+
68+
private int requestWidth;
69+
private int requestHeight;
70+
private int requestRate;
71+
72+
/**
73+
* Create an FXImageSink. A new AppSink element will be created that can be
74+
* accessed using {@link #getSinkElement()}.
75+
*/
6276
public FXImageSink() {
6377
this(new AppSink("FXImageSink"));
6478
}
65-
79+
80+
/**
81+
* Create and FXImageSink wrapping the provided AppSink element.
82+
*
83+
* @param sink AppSink element
84+
*/
6685
public FXImageSink(AppSink sink) {
6786
this.sink = sink;
6887
sink.set("emit-signals", true);
@@ -71,10 +90,95 @@ public FXImageSink(AppSink sink) {
7190
sink.connect(newSampleListener);
7291
sink.connect(newPrerollListener);
7392
sink.setCaps(Caps.fromString(DEFAULT_CAPS));
74-
image = new SimpleObjectProperty<>();
93+
image = new ReadOnlyObjectWrapper<>();
7594
pending = new AtomicReference<>();
7695
}
77-
96+
97+
/**
98+
* Property wrapping the current video frame as a JavaFX {@link Image}. The
99+
* Image should only be accessed on the JavaFX application thread. Use of
100+
* the Image when it is no longer the current value of this property may
101+
* cause errors or crashes.
102+
*
103+
* @return image property for current video frame
104+
*/
105+
public ReadOnlyObjectProperty<Image> imageProperty() {
106+
return image.getReadOnlyProperty();
107+
}
108+
109+
/**
110+
* Get access to the AppSink element this class wraps.
111+
*
112+
* @return AppSink element
113+
*/
114+
public AppSink getSinkElement() {
115+
return sink;
116+
}
117+
118+
/**
119+
* Clear any image and dispose of underlying native buffers. Can be called
120+
* from any thread, but clearing will happen asynchronously if not called on
121+
* JavaFX application thread.
122+
*/
123+
public void clear() {
124+
if (Platform.isFxApplicationThread()) {
125+
clearImage();
126+
} else {
127+
Platform.runLater(() -> clearImage());
128+
}
129+
}
130+
131+
/**
132+
* Request the given frame size for each video frame. This will set up the
133+
* Caps on the wrapped AppSink. Values are in pixels. A value of zero or
134+
* less will result in the value being omitted from the Caps.
135+
*
136+
* @param width pixel width
137+
* @param height pixel height
138+
* @return this for chaining
139+
*/
140+
public FXImageSink requestFrameSize(int width, int height) {
141+
this.requestWidth = width;
142+
this.requestHeight = height;
143+
sink.setCaps(Caps.fromString(buildCapsString()));
144+
return this;
145+
}
146+
147+
/**
148+
* Request the given frame rate. This will set up the Caps on the wrapped
149+
* AppSink. Value is in frames per second. A value of zero or less will
150+
* result in the value being omitted from the Caps.
151+
*
152+
* @param rate frame rate in frames per second
153+
* @return this for chaining
154+
*/
155+
public FXImageSink requestFrameRate(double rate) {
156+
requestRate = (int) Math.round(rate);
157+
sink.setCaps(Caps.fromString(buildCapsString()));
158+
return this;
159+
}
160+
161+
private String buildCapsString() {
162+
if (requestWidth < 1 && requestHeight < 1 && requestRate < 1) {
163+
return DEFAULT_CAPS;
164+
}
165+
StringBuilder sb = new StringBuilder(DEFAULT_CAPS);
166+
if (requestWidth > 0) {
167+
sb.append(",width=");
168+
sb.append(requestWidth);
169+
}
170+
if (requestHeight > 0) {
171+
sb.append(",height=");
172+
sb.append(requestHeight);
173+
}
174+
if (requestRate > 0) {
175+
sb.append(",framerate=");
176+
sb.append(requestRate);
177+
sb.append("/1");
178+
}
179+
return sb.toString();
180+
}
181+
78182
private void updateImage() {
79183
if (!Platform.isFxApplicationThread()) {
80184
throw new IllegalStateException("Not on FX application thread");
@@ -85,13 +189,13 @@ private void updateImage() {
85189
}
86190
Sample oldSample = activeSample;
87191
Buffer oldBuffer = activeBuffer;
88-
192+
89193
activeSample = newSample;
90194
Structure capsStruct = newSample.getCaps().getStructure(0);
91195
int width = capsStruct.getInteger("width");
92196
int height = capsStruct.getInteger("height");
93197
activeBuffer = newSample.getBuffer();
94-
198+
95199
PixelBuffer<ByteBuffer> pixelBuffer = new PixelBuffer(width, height,
96200
activeBuffer.map(false), PixelFormat.getByteBgraPreInstance());
97201
WritableImage img = new WritableImage(pixelBuffer);
@@ -103,46 +207,58 @@ private void updateImage() {
103207
if (oldSample != null) {
104208
oldSample.dispose();
105209
}
106-
107-
}
108-
109-
public ObjectProperty<Image> imageProperty() {
110-
return image;
210+
111211
}
112-
113-
public AppSink getElement() {
114-
return sink;
212+
213+
private void clearImage() {
214+
if (!Platform.isFxApplicationThread()) {
215+
throw new IllegalStateException("Not on FX application thread");
216+
}
217+
Sample newSample = pending.getAndSet(null);
218+
if (newSample != null) {
219+
newSample.dispose();
220+
}
221+
image.set(null);
222+
if (activeBuffer != null) {
223+
activeBuffer.unmap();
224+
activeBuffer = null;
225+
}
226+
if (activeSample != null) {
227+
activeSample.dispose();
228+
activeSample = null;
229+
}
115230
}
116-
117-
231+
118232
private class NewSampleListener implements AppSink.NEW_SAMPLE {
119233

120234
@Override
121235
public FlowReturn newSample(AppSink appsink) {
122236
Sample s = appsink.pullSample();
123237
s = pending.getAndSet(s);
124238
if (s != null) {
239+
// if not null the Sample has not been taken by the application thread so dispose
125240
s.dispose();
126241
}
127242
Platform.runLater(() -> updateImage());
128243
return FlowReturn.OK;
129244
}
130-
245+
131246
}
132-
247+
133248
private class NewPrerollListener implements AppSink.NEW_PREROLL {
134249

135250
@Override
136251
public FlowReturn newPreroll(AppSink appsink) {
137252
Sample s = appsink.pullPreroll();
138253
s = pending.getAndSet(s);
139254
if (s != null) {
255+
// if not null the Sample has not been taken by the application thread so dispose
140256
s.dispose();
141257
}
142258
Platform.runLater(() -> updateImage());
143259
return FlowReturn.OK;
144260
}
145-
261+
146262
}
147-
263+
148264
}

0 commit comments

Comments
 (0)