001 package com.github.sarxos.webcam.ds.jmf;
002
003 import java.awt.Dimension;
004 import java.awt.Graphics2D;
005 import java.awt.Image;
006 import java.awt.image.BufferedImage;
007 import java.util.ArrayList;
008 import java.util.Collections;
009 import java.util.Comparator;
010 import java.util.List;
011 import java.util.concurrent.Semaphore;
012
013 import javax.media.Buffer;
014 import javax.media.CaptureDeviceInfo;
015 import javax.media.Control;
016 import javax.media.Controller;
017 import javax.media.ControllerEvent;
018 import javax.media.ControllerListener;
019 import javax.media.Format;
020 import javax.media.Manager;
021 import javax.media.MediaLocator;
022 import javax.media.Player;
023 import javax.media.ResourceUnavailableEvent;
024 import javax.media.StartEvent;
025 import javax.media.control.FormatControl;
026 import javax.media.control.FrameGrabbingControl;
027 import javax.media.format.VideoFormat;
028 import javax.media.util.BufferToImage;
029
030 import org.slf4j.Logger;
031 import org.slf4j.LoggerFactory;
032
033 import com.github.sarxos.webcam.WebcamDevice;
034
035
036 /**
037 * Webcam device - JMF and FMJ implementation.
038 *
039 * @author Bartosz Firyn (SarXos)
040 */
041 public class JmfDevice implements WebcamDevice {
042
043 private static final Logger LOG = LoggerFactory.getLogger(JmfDevice.class);
044
045 /**
046 * Control to control format.
047 */
048 private final static String FORMAT_CTRL = "javax.media.control.FormatControl";
049
050 /**
051 * Control to grab frames.
052 */
053 private final static String GRABBING_CTRL = "javax.media.control.FrameGrabbingControl";
054
055 /**
056 * Player starter.
057 */
058 private class PlayerStarter extends Thread implements ControllerListener {
059
060 public PlayerStarter(Player player) {
061 setDaemon(true);
062 player.addControllerListener(this);
063 player.start();
064 }
065
066 @Override
067 public void run() {
068
069 // wait for start
070 while (!started) {
071 try {
072 Thread.sleep(10);
073 } catch (InterruptedException e) {
074 e.printStackTrace();
075 }
076 started = player.getState() == Controller.Started;
077 if (started) {
078 break;
079 }
080 }
081
082 // try to grab single image (wait 10 seconds)
083 for (int i = 0; i < 100; i++) {
084 Buffer buffer = grabber.grabFrame();
085 if (buffer.getLength() == 0) {
086 try {
087 Thread.sleep(100);
088 } catch (InterruptedException e) {
089 e.printStackTrace();
090 }
091 }
092 }
093
094 semaphore.release();
095 }
096
097 @Override
098 public void controllerUpdate(ControllerEvent ce) {
099 if (ce instanceof StartEvent) {
100 available = true;
101 semaphore.release();
102 }
103 if (ce instanceof ResourceUnavailableEvent) {
104 available = false;
105 semaphore.release();
106 }
107 }
108 }
109
110 /**
111 * Is webcam open.
112 */
113 private volatile boolean open = false;
114
115 /**
116 * Is player available.
117 */
118 private volatile boolean available = false;
119
120 /**
121 * Is player started.
122 */
123 private volatile boolean started = false;
124
125 private volatile boolean disposed = false;
126
127 private PlayerStarter starter = null;
128 private Semaphore semaphore = new Semaphore(0, true);
129
130 private CaptureDeviceInfo cdi = null;
131 private List<Dimension> dimensions = null;
132 private Dimension dimension = null;
133
134 private CaptureDeviceInfo device = null;
135 private MediaLocator locator = null;
136 private Player player = null;
137 private VideoFormat format = null;
138 private FormatControl control = null;
139 private FrameGrabbingControl grabber = null;
140 private BufferToImage converter = null;
141
142 private Dimension viewSize = null;
143
144 public JmfDevice(CaptureDeviceInfo cdi) {
145 this.cdi = cdi;
146 }
147
148 @Override
149 public String getName() {
150 return cdi.getName();
151 }
152
153 private VideoFormat createFormat(Dimension size) {
154 if (size == null) {
155 return getLargestVideoFormat();
156 } else {
157 return getSizedVideoFormat(size);
158 }
159 }
160
161 /**
162 * Get player control.
163 *
164 * @param name control name
165 * @return Player control
166 */
167 private Control getControl(String name) {
168 Control control = player.getControl(name);
169 if (control == null) {
170 throw new RuntimeException("Cannot find format control " + name);
171 }
172 return control;
173 }
174
175 /**
176 * Get video format for size.
177 *
178 * @param device device to get format from
179 * @param size specific size to search
180 * @return VideoFormat
181 */
182 private VideoFormat getSizedVideoFormat(Dimension size) {
183
184 Format[] formats = device.getFormats();
185 VideoFormat format = null;
186
187 for (Format f : formats) {
188
189 if (!"RGB".equalsIgnoreCase(f.getEncoding()) || !(f instanceof VideoFormat)) {
190 continue;
191 }
192
193 Dimension d = ((VideoFormat) f).getSize();
194 if (d.width == size.width && d.height == size.height) {
195 format = (VideoFormat) f;
196 break;
197 }
198 }
199
200 return format;
201 }
202
203 /**
204 * Get suitable video format to use (the largest one by default, but this
205 * can be easily changed).
206 *
207 * @param device device to get video format for
208 * @return Suitable video format
209 */
210 private VideoFormat getLargestVideoFormat() {
211
212 Format[] formats = device.getFormats();
213 VideoFormat format = null;
214 int area = 0;
215
216 // find the largest picture format
217 for (Format f : formats) {
218
219 if (!(f instanceof VideoFormat) || !"RGB".equalsIgnoreCase(f.getEncoding())) {
220 continue;
221 }
222
223 VideoFormat vf = (VideoFormat) f;
224 Dimension dim = vf.getSize();
225
226 int a = dim.width * dim.height;
227 if (a > area) {
228 area = a;
229 format = vf;
230 }
231 }
232
233 return format;
234 }
235
236 @Override
237 public Dimension[] getResolutions() {
238
239 if (dimensions == null) {
240 dimensions = new ArrayList<Dimension>();
241
242 Format[] formats = cdi.getFormats();
243 for (Format format : formats) {
244 if ("RGB".equalsIgnoreCase(format.getEncoding())) {
245 dimensions.add(((VideoFormat) format).getSize());
246 }
247 }
248
249 Collections.sort(dimensions, new Comparator<Dimension>() {
250
251 @Override
252 public int compare(Dimension a, Dimension b) {
253 int apx = a.width * a.height;
254 int bpx = b.width * b.height;
255 if (apx > bpx) {
256 return 1;
257 } else if (apx < bpx) {
258 return -1;
259 } else {
260 return 0;
261 }
262 }
263 });
264 }
265
266 return dimensions.toArray(new Dimension[dimensions.size()]);
267 }
268
269 @Override
270 public Dimension getResolution() {
271 return dimension;
272 }
273
274 @Override
275 public void setResolution(Dimension size) {
276 this.dimension = size;
277 }
278
279 @Override
280 public BufferedImage getImage() {
281
282 if (!open) {
283 throw new RuntimeException("Webcam has to be open to get image");
284 }
285
286 Buffer buffer = grabber.grabFrame();
287 Image image = converter.createImage(buffer);
288
289 if (image == null) {
290 throw new RuntimeException("Cannot get image");
291 }
292
293 int width = image.getWidth(null);
294 int height = image.getHeight(null);
295 int type = BufferedImage.TYPE_INT_RGB;
296
297 BufferedImage buffered = new BufferedImage(width, height, type);
298
299 Graphics2D g2 = buffered.createGraphics();
300 g2.drawImage(image, null, null);
301 g2.dispose();
302 buffered.flush();
303
304 return buffered;
305 }
306
307 @Override
308 public void open() {
309
310 if (disposed) {
311 LOG.warn("Cannot open device because it's already disposed");
312 return;
313 }
314
315 if (open) {
316 LOG.debug("Opening webcam - already open!");
317 return;
318 }
319
320 LOG.debug("Opening webcam");
321
322 locator = device.getLocator();
323 format = createFormat(viewSize);
324 converter = new BufferToImage(format);
325
326 try {
327 player = Manager.createRealizedPlayer(locator);
328 } catch (Exception e) {
329 throw new RuntimeException(e);
330 }
331
332 control = (FormatControl) getControl(FORMAT_CTRL);
333 grabber = (FrameGrabbingControl) getControl(GRABBING_CTRL);
334
335 if (control.setFormat(format) == null) {
336 throw new RuntimeException("Cannot change video format");
337 }
338
339 starter = new PlayerStarter(player);
340 starter.start();
341
342 try {
343 semaphore.acquire(2);
344 starter.join();
345 } catch (InterruptedException e) {
346 }
347
348 do {
349 BufferedImage image = getImage();
350 if (image != null) {
351 break;
352 }
353 try {
354 Thread.sleep(100);
355 } catch (InterruptedException e) {
356 }
357 } while (true);
358
359 open = started && available;
360 }
361
362 @Override
363 public void close() {
364
365 if (!started && !available && !open) {
366 LOG.debug("Cannot close webcam");
367 return;
368 }
369
370 LOG.debug("Closing webcam");
371
372 open = false;
373
374 if (started || available) {
375
376 started = false;
377 available = false;
378
379 player.stop();
380 player.close();
381 player.deallocate();
382 }
383 }
384
385 @Override
386 public void dispose() {
387 disposed = true;
388 }
389
390 @Override
391 public boolean isOpen() {
392 return open;
393 }
394
395 }