001    package com.github.sarxos.webcam.ds.civil;
002    
003    import java.awt.Dimension;
004    import java.awt.image.BufferedImage;
005    import java.util.ArrayList;
006    import java.util.Collections;
007    import java.util.Comparator;
008    import java.util.HashSet;
009    import java.util.List;
010    import java.util.Set;
011    import java.util.concurrent.atomic.AtomicBoolean;
012    
013    import org.slf4j.Logger;
014    import org.slf4j.LoggerFactory;
015    
016    import com.github.sarxos.webcam.WebcamDevice;
017    import com.lti.civil.CaptureDeviceInfo;
018    import com.lti.civil.CaptureException;
019    import com.lti.civil.CaptureObserver;
020    import com.lti.civil.CaptureStream;
021    import com.lti.civil.CaptureSystem;
022    import com.lti.civil.Image;
023    import com.lti.civil.VideoFormat;
024    import com.lti.civil.awt.AWTImageConverter;
025    
026    
027    /**
028     * Webcam device - LTI-CIVIL framework compatible implementation.
029     * 
030     * @author Bartosz Firyn (SarXos)
031     */
032    public class LtiCivilDevice implements WebcamDevice, CaptureObserver, WebcamDevice.FPSSource {
033    
034            private static final Logger LOG = LoggerFactory.getLogger(LtiCivilDevice.class);
035    
036            private CaptureDeviceInfo cdi = null;
037            private List<Dimension> dimensions = null;
038            private Dimension size = null;
039            private Image image = null;
040            private CaptureStream stream = null;
041    
042            private AtomicBoolean open = new AtomicBoolean(false);
043    
044            private volatile boolean capturing = false;
045            private volatile boolean disposed = false;
046    
047            private long t1 = -1;
048            private long t2 = -1;
049    
050            private volatile double fps = 0;
051    
052            protected LtiCivilDevice(CaptureDeviceInfo cdi) {
053                    this.cdi = cdi;
054            }
055    
056            @Override
057            public String getName() {
058                    return cdi.getDescription();
059            }
060    
061            @Override
062            public Dimension[] getResolutions() {
063    
064                    if (dimensions == null) {
065                            dimensions = new ArrayList<Dimension>();
066    
067                            CaptureSystem system = LtiCivilDriver.getCaptureSystem();
068                            Set<Dimension> set = new HashSet<Dimension>();
069    
070                            try {
071    
072                                    stream = system.openCaptureDeviceStream(cdi.getDeviceID());
073    
074                                    for (VideoFormat format : stream.enumVideoFormats()) {
075                                            if (format.getFormatType() == VideoFormat.RGB24) {
076                                                    set.add(new Dimension(format.getWidth(), format.getHeight()));
077                                            }
078                                    }
079    
080                                    stream.dispose();
081    
082                            } catch (CaptureException e) {
083                                    LOG.error("Capture exception when collecting formats dimension", e);
084                            }
085    
086                            dimensions.addAll(set);
087    
088                            Collections.sort(dimensions, new Comparator<Dimension>() {
089    
090                                    @Override
091                                    public int compare(Dimension a, Dimension b) {
092                                            int apx = a.width * a.height;
093                                            int bpx = b.width * b.height;
094                                            if (apx > bpx) {
095                                                    return 1;
096                                            } else if (apx < bpx) {
097                                                    return -1;
098                                            } else {
099                                                    return 0;
100                                            }
101                                    }
102                            });
103                    }
104    
105                    return dimensions.toArray(new Dimension[dimensions.size()]);
106            }
107    
108            @Override
109            public BufferedImage getImage() {
110                    if (!capturing) {
111                            return null;
112                    }
113                    return AWTImageConverter.toBufferedImage(image);
114            }
115    
116            @Override
117            public void onError(CaptureStream stream, CaptureException e) {
118                    LOG.error("Exception in capture stream", e);
119            }
120    
121            @Override
122            public void onNewImage(CaptureStream stream, Image image) {
123    
124                    if (t1 == -1 || t2 == -1) {
125                            t1 = System.currentTimeMillis();
126                            t2 = System.currentTimeMillis();
127                    }
128    
129                    this.image = image;
130                    this.capturing = true;
131    
132                    t1 = t2;
133                    t2 = System.currentTimeMillis();
134    
135                    fps = (4 * fps + 1000 / (t2 - t1 + 1)) / 5;
136            }
137    
138            @Override
139            public void open() {
140    
141                    if (disposed) {
142                            return;
143                    }
144    
145                    if (open.compareAndSet(false, true)) {
146    
147                            try {
148                                    stream = LtiCivilDriver.getCaptureSystem().openCaptureDeviceStream(cdi.getDeviceID());
149                                    stream.setVideoFormat(findFormat());
150                                    stream.setObserver(this);
151                                    stream.start();
152                            } catch (CaptureException e) {
153                                    LOG.error("Capture exception when opening Civil device", e);
154                            }
155                    }
156    
157                    while (true) {
158                            if (capturing) {
159                                    break;
160                            }
161                            try {
162                                    Thread.sleep(100);
163                            } catch (InterruptedException e) {
164                                    return;
165                            }
166                    }
167            }
168    
169            private VideoFormat findFormat() {
170                    if (stream == null) {
171                            throw new RuntimeException("Stream is null");
172                    }
173                    if (size == null) {
174                            throw new RuntimeException("Size is not set");
175                    }
176                    try {
177                            for (VideoFormat format : stream.enumVideoFormats()) {
178                                    if (format.getFormatType() == VideoFormat.RGB24) {
179                                            boolean xok = size.width == format.getWidth();
180                                            boolean yok = size.height == format.getHeight();
181                                            if (xok && yok) {
182                                                    return format;
183                                            }
184                                    }
185                            }
186                    } catch (CaptureException e) {
187                            LOG.error("Capture exception when iterating thru video formats", e);
188                    }
189                    throw new RuntimeException("Cannot find RGB24 video format for size [" + size.width + "x" + size.height + "]");
190            }
191    
192            @Override
193            public void close() {
194                    if (open.compareAndSet(true, false)) {
195                            try {
196                                    stream.stop();
197                                    stream.dispose();
198                            } catch (CaptureException e) {
199                                    LOG.error("Capture exception when closing Civil device", e);
200                            }
201                    }
202            }
203    
204            @Override
205            public Dimension getResolution() {
206                    return size;
207            }
208    
209            @Override
210            public void setResolution(Dimension d) {
211                    this.size = d;
212            }
213    
214            @Override
215            public void dispose() {
216                    disposed = true;
217            }
218    
219            @Override
220            public boolean isOpen() {
221                    return open.get();
222            }
223    
224            @Override
225            public double getFPS() {
226                    return fps;
227            }
228    }