001package com.github.sarxos.webcam.ds.civil;
002
003import java.awt.Dimension;
004import java.awt.image.BufferedImage;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.Comparator;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Set;
011import java.util.concurrent.atomic.AtomicBoolean;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016import com.github.sarxos.webcam.WebcamDevice;
017import com.lti.civil.CaptureDeviceInfo;
018import com.lti.civil.CaptureException;
019import com.lti.civil.CaptureObserver;
020import com.lti.civil.CaptureStream;
021import com.lti.civil.CaptureSystem;
022import com.lti.civil.Image;
023import com.lti.civil.VideoFormat;
024import com.lti.civil.awt.AWTImageConverter;
025
026
027/**
028 * Webcam device - LTI-CIVIL framework compatible implementation.
029 * 
030 * @author Bartosz Firyn (SarXos)
031 */
032public 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}