Skip to content

Commit 3a238cf

Browse files
committed
Add example for parallel use of Webcam.getImageBytes(), refs #599
1 parent c8484d1 commit 3a238cf

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
import java.awt.Dimension;
2+
import java.awt.Transparency;
3+
import java.awt.color.ColorSpace;
4+
import java.awt.image.BufferedImage;
5+
import java.awt.image.ComponentColorModel;
6+
import java.awt.image.ComponentSampleModel;
7+
import java.awt.image.DataBuffer;
8+
import java.awt.image.DataBufferByte;
9+
import java.awt.image.Raster;
10+
import java.awt.image.WritableRaster;
11+
import java.nio.BufferUnderflowException;
12+
import java.nio.ByteBuffer;
13+
import java.util.Collections;
14+
import java.util.LinkedHashSet;
15+
import java.util.Set;
16+
import java.util.concurrent.Exchanger;
17+
import java.util.concurrent.TimeUnit;
18+
import java.util.concurrent.TimeoutException;
19+
import java.util.concurrent.atomic.AtomicReference;
20+
21+
import javax.swing.JFrame;
22+
23+
import com.github.sarxos.webcam.Webcam;
24+
import com.github.sarxos.webcam.WebcamPanel;
25+
import com.github.sarxos.webcam.WebcamPanel.ImageSupplier;
26+
import com.github.sarxos.webcam.WebcamResolution;
27+
28+
29+
/**
30+
* This example demonstrate how to implement exchange mechanism which will make
31+
* {@link Webcam#getImageBytes()} to run in parallel without causing FPS drop.
32+
*
33+
* @author Bartosz Firyn (sarxos)
34+
*/
35+
public class ParallelGetImageBytesExample {
36+
37+
private static class ByteBufferExchanger extends Exchanger<ByteBuffer> implements AutoCloseable {
38+
39+
private final AsyncWebcamBuffer owner;
40+
41+
public ByteBufferExchanger(final AsyncWebcamBuffer owner) {
42+
this.owner = owner;
43+
}
44+
45+
/**
46+
* Await for new {@link ByteBuffer} to be ready.
47+
*/
48+
public void await() {
49+
awaitAndGet();
50+
}
51+
52+
/**
53+
* Await for new {@link ByteBuffer} to be available and return it.
54+
*
55+
* @return The {@link ByteBuffer}
56+
*/
57+
public ByteBuffer awaitAndGet() {
58+
try {
59+
return exchange(null);
60+
} catch (InterruptedException e) {
61+
throw new IllegalStateException(e);
62+
}
63+
}
64+
65+
/**
66+
* To be used only from {@link AsyncWebcamBuffer}. Please do not invoke this method from the
67+
* other classes.
68+
*
69+
* @param bb the {@link ByteBuffer} to exchange
70+
*/
71+
public void ready(ByteBuffer bb) {
72+
try {
73+
exchange(bb, 0, TimeUnit.SECONDS);
74+
} catch (InterruptedException | TimeoutException e) {
75+
throw new IllegalStateException(e);
76+
}
77+
}
78+
79+
@Override
80+
public void close() {
81+
owner.dispose(this);
82+
}
83+
}
84+
85+
private static class AsyncWebcamBuffer extends Thread {
86+
87+
private final Webcam webcam;
88+
private AtomicReference<ByteBuffer> buffer = new AtomicReference<ByteBuffer>();
89+
private Set<ByteBufferExchanger> exchangers = Collections.synchronizedSet(new LinkedHashSet<ByteBufferExchanger>());
90+
private final int length;
91+
92+
public AsyncWebcamBuffer(Webcam webcam) {
93+
this.webcam = webcam;
94+
this.length = getLength(webcam.getViewSize());
95+
this.setDaemon(true);
96+
this.start();
97+
}
98+
99+
public int getLength(Dimension size) {
100+
return size.width * size.height * 3;
101+
}
102+
103+
public int length() {
104+
return length;
105+
}
106+
107+
public Webcam getWebcam() {
108+
return webcam;
109+
}
110+
111+
@Override
112+
public void run() {
113+
while (webcam.isOpen()) {
114+
115+
// get buffer from webcam (this is direct byte buffer located in off-heap memory)
116+
117+
final ByteBuffer bb = webcam.getImageBytes();
118+
bb.rewind();
119+
120+
buffer.set(bb);
121+
122+
// notify all exchangers
123+
124+
for (ByteBufferExchanger exchanger : exchangers) {
125+
exchanger.ready(bb);
126+
}
127+
}
128+
}
129+
130+
/**
131+
* Be careful when using this reference! It's non synchronized so you have to take special
132+
* care to synchronize and maintain position in buffer to avoid
133+
* {@link BufferUnderflowException}.
134+
*
135+
* @return Non synchronized {@link ByteBuffer}
136+
*/
137+
public ByteBuffer getByteBuffer() {
138+
return buffer.get();
139+
}
140+
141+
/**
142+
* @return New {@link ByteBufferExchanger}
143+
*/
144+
public ByteBufferExchanger exchanger() {
145+
final ByteBufferExchanger exchanger = new ByteBufferExchanger(this);
146+
exchangers.add(exchanger);
147+
return exchanger;
148+
}
149+
150+
public void dispose(ByteBufferExchanger exchanger) {
151+
exchangers.remove(exchanger);
152+
}
153+
154+
/**
155+
* Rewrite {@link ByteBuffer} data to the provided byte[] array.
156+
*
157+
* @param bytes the byte[] array to rewrite {@link ByteBuffer} into
158+
*/
159+
public void read(byte[] bytes) {
160+
final ByteBuffer buffer = getByteBuffer();
161+
// all operations on buffer need to be synchronized
162+
synchronized (buffer) {
163+
buffer.rewind();
164+
buffer.get(bytes);
165+
buffer.rewind();
166+
}
167+
}
168+
169+
/**
170+
* Rewrite {@link ByteBuffer} to newly created byte[] array and return it.
171+
*
172+
* @return Newly created byte[] array with data from {@link ByteBuffer}
173+
*/
174+
public byte[] read() {
175+
final byte[] bytes = new byte[length];
176+
final ByteBuffer buffer = getByteBuffer();
177+
// all operations on buffer need to be synchronized
178+
synchronized (buffer) {
179+
buffer.rewind();
180+
buffer.get(bytes);
181+
buffer.rewind();
182+
}
183+
return bytes;
184+
}
185+
186+
public boolean isReady() {
187+
return buffer.get() != null;
188+
}
189+
}
190+
191+
private static class WebcamPanelImageSupplier implements ImageSupplier {
192+
193+
private final int[] imageOffset = new int[] { 0 };
194+
private final int[] bandOffsets = new int[] { 0, 1, 2 };
195+
private final int[] bits = { 8, 8, 8 };
196+
private final int dataType = DataBuffer.TYPE_BYTE;
197+
private final Dimension size;
198+
private final AsyncWebcamBuffer buffer;
199+
private final ComponentSampleModel sampleModel;
200+
private final ColorSpace colorSpace;
201+
private final ComponentColorModel colorModel;
202+
203+
public WebcamPanelImageSupplier(AsyncWebcamBuffer buffer) {
204+
this.buffer = buffer;
205+
this.size = buffer.getWebcam().getViewSize();
206+
this.sampleModel = new ComponentSampleModel(dataType, size.width, size.height, 3, size.width * 3, bandOffsets);
207+
this.colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
208+
this.colorModel = new ComponentColorModel(colorSpace, bits, false, false, Transparency.OPAQUE, dataType);
209+
}
210+
211+
@Override
212+
public BufferedImage get() {
213+
214+
while (!buffer.isReady()) {
215+
return null;
216+
}
217+
218+
final byte[] bytes = new byte[size.width * size.height * 3];
219+
final byte[][] data = new byte[][] { bytes };
220+
221+
buffer.read(bytes);
222+
223+
final DataBufferByte dataBuffer = new DataBufferByte(data, bytes.length, imageOffset);
224+
final WritableRaster raster = Raster.createWritableRaster(sampleModel, dataBuffer, null);
225+
final BufferedImage image = new BufferedImage(colorModel, raster, false, null);
226+
227+
return image;
228+
}
229+
}
230+
231+
public static void main(String[] args) throws InterruptedException {
232+
233+
final Dimension size = WebcamResolution.VGA.getSize();
234+
235+
final Webcam webcam = Webcam.getDefault();
236+
webcam.setViewSize(size);
237+
webcam.open();
238+
239+
final AsyncWebcamBuffer buffer = new AsyncWebcamBuffer(webcam);
240+
final ImageSupplier supplier = new WebcamPanelImageSupplier(buffer);
241+
242+
final WebcamPanel panel = new WebcamPanel(webcam, size, true, supplier);
243+
panel.setFPSDisplayed(true);
244+
panel.setDisplayDebugInfo(true);
245+
panel.setImageSizeDisplayed(true);
246+
panel.setMirrored(true);
247+
248+
final JFrame window = new JFrame("Test webcam panel");
249+
window.add(panel);
250+
window.setResizable(true);
251+
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
252+
window.pack();
253+
window.setVisible(true);
254+
255+
// this thread will get underlying ByteBuffer and perform synchronized op to
256+
// get rewrite it into bytes[] array
257+
258+
final Thread t1 = new Thread() {
259+
260+
@Override
261+
public void run() {
262+
263+
// make sure to close exchanger because you will end up with memory leak
264+
265+
try (final ByteBufferExchanger exchanger = buffer.exchanger()) {
266+
267+
while (webcam.isOpen()) {
268+
269+
long t1 = System.currentTimeMillis();
270+
final ByteBuffer bb = exchanger.awaitAndGet();
271+
long t2 = System.currentTimeMillis();
272+
273+
System.out.println(getName() + " : " + 1000 / (t2 - t1 + 1));
274+
275+
final byte[] bytes = new byte[buffer.length];
276+
277+
// make sure to synchronize or you will end up
278+
279+
synchronized (bb) {
280+
bb.rewind();
281+
bb.get(bytes);
282+
bb.rewind();
283+
}
284+
285+
// do processing on bytes[] array
286+
}
287+
}
288+
}
289+
};
290+
t1.start();
291+
292+
// this thread will await for underlying ByteBuffer to be ready and perform
293+
// synchronized op to get rewrite it into new bytes[] array
294+
295+
final Thread t2 = new Thread() {
296+
297+
@Override
298+
public void run() {
299+
300+
try (final ByteBufferExchanger exchanger = buffer.exchanger()) {
301+
while (webcam.isOpen()) {
302+
303+
long t1 = System.currentTimeMillis();
304+
exchanger.await();
305+
long t2 = System.currentTimeMillis();
306+
307+
System.out.println(getName() + " : " + 1000 / (t2 - t1 + 1));
308+
309+
final byte[] bytes = buffer.read();
310+
311+
// do processing on bytes[] array
312+
}
313+
}
314+
}
315+
};
316+
t2.start();
317+
318+
// this thread will await for underlying ByteBuffer to be ready and perform
319+
// synchronized op to get rewrite it into pre-created bytes[] array
320+
321+
final Thread t3 = new Thread() {
322+
323+
@Override
324+
public void run() {
325+
326+
try (final ByteBufferExchanger exchanger = buffer.exchanger()) {
327+
328+
final byte[] bytes = new byte[buffer.length()];
329+
330+
while (webcam.isOpen()) {
331+
332+
long t1 = System.currentTimeMillis();
333+
exchanger.await();
334+
long t2 = System.currentTimeMillis();
335+
336+
System.out.println(getName() + " : " + 1000 / (t2 - t1 + 1));
337+
338+
buffer.read(bytes);
339+
340+
// do processing on bytes[] array
341+
}
342+
}
343+
}
344+
};
345+
t3.start();
346+
}
347+
}

0 commit comments

Comments
 (0)