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 }