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    }