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}