Skip to content

Commit b6e9044

Browse files
committed
Add MJPEG support in GStreamer driver, refs #145
1 parent 524f7ba commit b6e9044

File tree

4 files changed

+157
-56
lines changed

4 files changed

+157
-56
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import static com.github.sarxos.webcam.ds.gstreamer.GStreamerDriver.FORMAT_MJPEG;
2+
import static com.github.sarxos.webcam.ds.gstreamer.GStreamerDriver.FORMAT_RGB;
3+
import static com.github.sarxos.webcam.ds.gstreamer.GStreamerDriver.FORMAT_YUV;
4+
5+
import java.util.Arrays;
6+
7+
import javax.swing.JFrame;
8+
9+
import com.github.sarxos.webcam.Webcam;
10+
import com.github.sarxos.webcam.WebcamPanel;
11+
import com.github.sarxos.webcam.ds.gstreamer.GStreamerDriver;
12+
13+
14+
public class GStreamerDriverMjpegExample {
15+
16+
static {
17+
Webcam.setDriver(new GStreamerDriver(Arrays.asList(FORMAT_MJPEG, FORMAT_RGB, FORMAT_YUV)));
18+
}
19+
20+
public static void main(String[] args) {
21+
22+
WebcamPanel panel = new WebcamPanel(Webcam.getWebcams().get(0));
23+
panel.setFPSDisplayed(true);
24+
25+
JFrame frame = new JFrame("GStreamer Webcam Capture Driver Demo");
26+
frame.add(panel);
27+
frame.pack();
28+
frame.setVisible(true);
29+
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
30+
}
31+
}

webcam-capture-drivers/driver-gstreamer/src/example/java/WebcamPanelExample.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.sarxos.webcam.Webcam;
44
import com.github.sarxos.webcam.WebcamPanel;
5+
import com.github.sarxos.webcam.WebcamPanel.DrawMode;
56
import com.github.sarxos.webcam.WebcamResolution;
67
import com.github.sarxos.webcam.ds.gstreamer.GStreamerDriver;
78

@@ -14,15 +15,16 @@ public class WebcamPanelExample {
1415

1516
public static void main(String[] args) {
1617

18+
WebcamResolution resolution = WebcamResolution.HD;
1719
Webcam webcam = Webcam.getDefault();
18-
webcam.setViewSize(WebcamResolution.HD720.getSize());
20+
webcam.setViewSize(resolution.getSize());
1921

2022
WebcamPanel panel = new WebcamPanel(webcam);
21-
panel.setDisplayDebugInfo(true);
2223
panel.setFPSDisplayed(true);
23-
panel.setFillArea(true);
24+
panel.setDrawMode(DrawMode.FIT);
25+
panel.setImageSizeDisplayed(true);
2426

25-
JFrame window = new JFrame("Test webcam panel");
27+
JFrame window = new JFrame(webcam + " @ " + resolution);
2628
window.add(panel);
2729
window.setResizable(true);
2830
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

webcam-capture-drivers/driver-gstreamer/src/main/java/com/github/sarxos/webcam/ds/gstreamer/GStreamerDevice.java

Lines changed: 85 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.github.sarxos.webcam.ds.gstreamer;
22

3+
import static com.github.sarxos.webcam.ds.gstreamer.GStreamerDriver.FORMAT_MJPEG;
4+
35
import java.awt.Dimension;
46
import java.awt.image.BufferedImage;
57
import java.awt.image.DataBufferInt;
@@ -39,16 +41,6 @@ public class GStreamerDevice implements WebcamDevice, RGBDataSink.Listener, Webc
3941
*/
4042
private static final long LATENESS = 20; // ms
4143

42-
/**
43-
* First formats are better. For example video/x-raw-rgb gives 30 FPS on HD720p where
44-
* video/x-raw-yuv only 10 FPS on the same resolution. The goal is to use these "better" formats
45-
* first, and then fallback to less efficient when not available.
46-
*/
47-
private static final String[] BEST_FORMATS = {
48-
"video/x-raw-rgb",
49-
"video/x-raw-yuv",
50-
};
51-
5244
/**
5345
* Video format to capture.
5446
*/
@@ -68,11 +60,16 @@ public class GStreamerDevice implements WebcamDevice, RGBDataSink.Listener, Webc
6860
*/
6961
private final File vfile;
7062

63+
private final GStreamerDriver driver;
64+
7165
/* gstreamer stuff */
7266

7367
private Pipeline pipe = null;
7468
private Element source = null;
7569
private Element filter = null;
70+
private Element jpegpar = null;
71+
private Element jpegdec = null;
72+
private Element[] elements = null;
7673
private RGBDataSink sink = null;
7774

7875
private Caps caps = null;
@@ -98,12 +95,14 @@ public class GStreamerDevice implements WebcamDevice, RGBDataSink.Listener, Webc
9895
*
9996
* @param name the name of webcam device
10097
*/
101-
protected GStreamerDevice(String name) {
98+
protected GStreamerDevice(GStreamerDriver driver, String name) {
99+
this.driver = driver;
102100
this.name = name;
103101
this.vfile = null;
104102
}
105103

106-
protected GStreamerDevice(File vfile) {
104+
protected GStreamerDevice(GStreamerDriver driver, File vfile) {
105+
this.driver = driver;
107106
this.name = null;
108107
this.vfile = vfile;
109108
}
@@ -122,10 +121,10 @@ private synchronized void init() {
122121
pipe = new Pipeline(name);
123122

124123
if (Platform.isWindows()) {
125-
source = ElementFactory.make("dshowvideosrc", "source");
124+
source = ElementFactory.make("dshowvideosrc", "dshowvideosrc");
126125
source.set("device-name", name);
127126
} else if (Platform.isLinux()) {
128-
source = ElementFactory.make("v4l2src", "source");
127+
source = ElementFactory.make("v4l2src", "v4l2src");
129128
source.set("device", vfile.getAbsolutePath());
130129
}
131130

@@ -134,21 +133,20 @@ private synchronized void init() {
134133
sink.getSinkElement().setMaximumLateness(LATENESS, TimeUnit.MILLISECONDS);
135134
sink.getSinkElement().setQOSEnabled(true);
136135

137-
filter = ElementFactory.make("capsfilter", "filter");
136+
filter = ElementFactory.make("capsfilter", "capsfilter");
138137

139-
if (Platform.isLinux()) {
140-
pipe.addMany(source, filter, sink);
141-
Element.linkMany(source, filter, sink);
142-
pipe.setState(State.READY);
143-
}
138+
jpegpar = ElementFactory.make("jpegparse", "jpegparse");
139+
jpegdec = ElementFactory.make("jpegdec", "jpegdec");
140+
141+
// if (Platform.isLinux()) {
142+
pipelineReady();
143+
// }
144144

145145
resolutions = parseResolutions(source.getPads().get(0));
146146

147-
if (Platform.isLinux()) {
148-
pipe.setState(State.NULL);
149-
Element.unlinkMany(source, filter, sink);
150-
pipe.removeMany(source, filter, sink);
151-
}
147+
// if (Platform.isLinux()) {
148+
pipelineStop();
149+
// }
152150
}
153151

154152
/**
@@ -161,15 +159,15 @@ private Dimension[] parseResolutions(Pad pad) {
161159

162160
Caps caps = pad.getCaps();
163161

164-
format = findBestFormat(caps);
162+
format = findPreferredFormat(caps);
165163

166164
LOG.debug("Best format is {}", format);
167165

168166
Dimension r = null;
169167
Structure s = null;
170168
String mime = null;
171169

172-
int n = caps.size();
170+
final int n = caps.size();
173171
int i = 0;
174172

175173
Map<String, Dimension> map = new HashMap<String, Dimension>();
@@ -190,19 +188,19 @@ private Dimension[] parseResolutions(Pad pad) {
190188

191189
} while (i < n);
192190

193-
Dimension[] resolutions = new ArrayList<Dimension>(map.values()).toArray(new Dimension[map.size()]);
191+
final Dimension[] resolutions = new ArrayList<Dimension>(map.values()).toArray(new Dimension[0]);
194192

195193
if (LOG.isDebugEnabled()) {
196194
for (Dimension d : resolutions) {
197-
LOG.debug("Resolution detected {}", d);
195+
LOG.debug("Resolution detected {} with format {}", d, format);
198196
}
199197
}
200198

201199
return resolutions;
202200
}
203201

204-
private static String findBestFormat(Caps caps) {
205-
for (String f : BEST_FORMATS) {
202+
private String findPreferredFormat(Caps caps) {
203+
for (String f : driver.getPreferredFormats()) {
206204
for (int i = 0, n = caps.size(); i < n; i++) {
207205
if (f.equals(caps.getStructure(i).getName())) {
208206
return f;
@@ -287,19 +285,17 @@ public void open() {
287285
caps.dispose();
288286
}
289287

290-
caps = Caps.fromString(String.format("%s,width=%d,height=%d", format, size.width, size.height));
291-
288+
caps = Caps.fromString(String.format("%s,framerate=30/1,width=%d,height=%d", format, size.width, size.height));
292289
filter.setCaps(caps);
293290

294-
LOG.debug("Link elements");
291+
LOG.debug("Using filter caps: {}", caps);
295292

296-
pipe.addMany(source, filter, sink);
297-
Element.linkMany(source, filter, sink);
298-
pipe.setState(State.PLAYING);
293+
pipelinePlay();
294+
295+
LOG.debug("Wait for device to be ready");
299296

300297
// wait max 20s for image to appear
301298
synchronized (this) {
302-
LOG.debug("Wait for device to be ready");
303299
try {
304300
this.wait(20000);
305301
} catch (InterruptedException e) {
@@ -308,6 +304,51 @@ public void open() {
308304
}
309305
}
310306

307+
private void pipelineElementsReset() {
308+
elements = null;
309+
}
310+
311+
private Element[] pipelineElementsPrepare() {
312+
if (elements == null) {
313+
if (FORMAT_MJPEG.equals(format)) {
314+
elements = new Element[] { source, filter, jpegpar, jpegdec, sink };
315+
} else {
316+
elements = new Element[] { source, filter, sink };
317+
}
318+
}
319+
return elements;
320+
}
321+
322+
private void pipelineElementsLink() {
323+
final Element[] elements = pipelineElementsPrepare();
324+
pipe.addMany(elements);
325+
if (!Element.linkMany(elements)) {
326+
LOG.warn("Some elements were not successfully linked!");
327+
}
328+
}
329+
330+
private void pipelineElementsUnlink() {
331+
final Element[] elements = pipelineElementsPrepare();
332+
Element.unlinkMany(elements);
333+
pipe.removeMany(elements);
334+
}
335+
336+
private void pipelineReady() {
337+
pipelineElementsLink();
338+
pipe.setState(State.READY);
339+
}
340+
341+
private void pipelinePlay() {
342+
pipelineElementsReset();
343+
pipelineElementsLink();
344+
pipe.setState(State.PLAYING);
345+
}
346+
347+
private void pipelineStop() {
348+
pipe.setState(State.NULL);
349+
pipelineElementsUnlink();
350+
}
351+
311352
@Override
312353
public void close() {
313354

@@ -317,13 +358,9 @@ public void close() {
317358

318359
LOG.debug("Closing GStreamer device");
319360

320-
image = null;
321-
322-
LOG.debug("Unlink elements");
361+
pipelineStop();
323362

324-
pipe.setState(State.NULL);
325-
Element.unlinkMany(source, filter, sink);
326-
pipe.removeMany(source, filter, sink);
363+
image = null;
327364
}
328365

329366
@Override
@@ -337,11 +374,13 @@ public void dispose() {
337374

338375
close();
339376

340-
filter.dispose();
341377
source.dispose();
378+
filter.dispose();
379+
jpegpar.dispose();
380+
jpegdec.dispose();
381+
caps.dispose();
342382
sink.dispose();
343383
pipe.dispose();
344-
caps.dispose();
345384
}
346385

347386
@Override

webcam-capture-drivers/driver-gstreamer/src/main/java/com/github/sarxos/webcam/ds/gstreamer/GStreamerDriver.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.File;
44
import java.util.ArrayList;
5+
import java.util.Arrays;
56
import java.util.List;
67
import java.util.concurrent.atomic.AtomicBoolean;
78

@@ -45,13 +46,20 @@ public void run() {
4546
private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
4647

4748
public GStreamerDriver() {
48-
if (INITIALIZED.compareAndSet(false, true)) {
49-
init();
50-
}
49+
init();
50+
}
51+
52+
public GStreamerDriver(final List<String> preferredFormats) {
53+
init();
54+
setPreferredFormats(preferredFormats);
5155
}
5256

5357
private static final void init() {
5458

59+
if (!INITIALIZED.compareAndSet(false, true)) {
60+
return;
61+
}
62+
5563
if (!Platform.isWindows() && !Platform.isLinux()) {
5664
throw new WebcamException(String.format("%s has been designed to work only on Windows and Linux platforms", GStreamerDriver.class.getSimpleName()));
5765
}
@@ -92,6 +100,27 @@ private static final void init() {
92100
Runtime.getRuntime().addShutdownHook(new GStreamerShutdownHook());
93101
}
94102

103+
public static final String FORMAT_RGB = "video/x-raw-rgb";
104+
public static final String FORMAT_YUV = "video/x-raw-yuv";
105+
public static final String FORMAT_MJPEG = "image/jpeg";
106+
107+
private List<String> preferredFormats = new ArrayList<>(Arrays.asList(FORMAT_RGB, FORMAT_YUV, FORMAT_MJPEG));
108+
109+
/**
110+
* Set preferred video formats for this driver. First formats from the list are better and will
111+
* be selected if available.
112+
*/
113+
public void setPreferredFormats(List<String> preferredFormats) {
114+
if (preferredFormats.isEmpty()) {
115+
throw new IllegalArgumentException("Preferred formats list must not be empty");
116+
}
117+
this.preferredFormats = new ArrayList<>(preferredFormats);
118+
}
119+
120+
public List<String> getPreferredFormats() {
121+
return preferredFormats;
122+
}
123+
95124
@Override
96125
public List<WebcamDevice> getDevices() {
97126

@@ -106,17 +135,17 @@ public List<WebcamDevice> getDevices() {
106135
srcname = "qtkitvideosrc";
107136
}
108137

109-
Element src = ElementFactory.make(srcname, "source");
138+
final Element src = ElementFactory.make(srcname, "source");
110139

111140
try {
112141
if (Platform.isWindows()) {
113142
PropertyProbe probe = PropertyProbe.wrap(src);
114143
for (Object name : probe.getValues("device-name")) {
115-
devices.add(new GStreamerDevice(name.toString()));
144+
devices.add(new GStreamerDevice(this, name.toString()));
116145
}
117146
} else if (Platform.isLinux()) {
118147
for (File vfile : NixVideoDevUtils.getVideoFiles()) {
119-
devices.add(new GStreamerDevice(vfile));
148+
devices.add(new GStreamerDevice(this, vfile));
120149
}
121150
} else {
122151
throw new RuntimeException("Platform unsupported by GStreamer capture driver");

0 commit comments

Comments
 (0)