diff --git a/webcam-capture-drivers/driver-v4l4j/src/main/java/com/github/sarxos/webcam/ds/v4l4j/V4l4jDevice.java b/webcam-capture-drivers/driver-v4l4j/src/main/java/com/github/sarxos/webcam/ds/v4l4j/V4l4jDevice.java index 6f2cd5b8..ccf94999 100644 --- a/webcam-capture-drivers/driver-v4l4j/src/main/java/com/github/sarxos/webcam/ds/v4l4j/V4l4jDevice.java +++ b/webcam-capture-drivers/driver-v4l4j/src/main/java/com/github/sarxos/webcam/ds/v4l4j/V4l4jDevice.java @@ -18,6 +18,7 @@ import au.edu.jcu.v4l4j.ImageFormatList; import au.edu.jcu.v4l4j.ResolutionInfo; import au.edu.jcu.v4l4j.ResolutionInfo.DiscreteResolution; +import au.edu.jcu.v4l4j.ResolutionInfo.StepwiseResolution; import au.edu.jcu.v4l4j.VideoDevice; import au.edu.jcu.v4l4j.VideoFrame; import au.edu.jcu.v4l4j.exceptions.StateException; @@ -25,21 +26,42 @@ import com.github.sarxos.webcam.WebcamDevice; import com.github.sarxos.webcam.WebcamException; +import com.github.sarxos.webcam.WebcamResolution; public class V4l4jDevice implements WebcamDevice, CaptureCallback, WebcamDevice.FPSSource { private static final Logger LOG = LoggerFactory.getLogger(V4l4jDevice.class); - private final File vfile; + private static final String[] BEST_FORMATS = new String[] { + + // MJPEG and JPEG are the best match because there is no need to + // recalculate too much, hardware will deliver what we need + + "MJPEG", "JPEG", + + // next are YUV formats where every 2 pixels can be written in 4 bytes + + "YU", "UY", "YV", "UV", + + // 24-bit formats where every pixel can be stored in 3 bytes + + "BGR24", "RGB24", + + // 32-bit formats where every pixel can be stored in 4 bytes + + "BGR32", "RGB32" + }; + + private final File videoFile; + private final VideoDevice videoDevice; + // private final DeviceInfo videoDeviceInfo; + private final ImageFormat videoBestImageFormat; - private VideoDevice vd = null; - private DeviceInfo di = null; - private ImageFormatList ifl = null; private FrameGrabber grabber = null; + private final List videoResolutions; - private List formats = null; - private List resolutions = new ArrayList(); + // private final List formats; private Dimension resolution = null; private AtomicBoolean open = new AtomicBoolean(false); @@ -56,61 +78,185 @@ public class V4l4jDevice implements WebcamDevice, CaptureCallback, WebcamDevice. private volatile double fps = 0; - public V4l4jDevice(File vfile) { + public V4l4jDevice(File file) { + + if (file == null) { + throw new IllegalArgumentException("Video file cannot be null!"); + } + + videoFile = file; + videoDevice = getVideoDevice(file); + videoBestImageFormat = getVideoBestImageFormat(videoDevice); + videoResolutions = getVideoResolutions(videoBestImageFormat); + } - this.vfile = vfile; + /** + * Create video device from file. + * + * @param file the video descriptor file + * @return The {@link VideoDevice} + */ + private static VideoDevice getVideoDevice(File file) { - LOG.debug("Creating V4L4J devuce"); + LOG.debug("Creating V4L4J device from file {}", file); try { - vd = new VideoDevice(vfile.getAbsolutePath()); + return new VideoDevice(file.getAbsolutePath()); } catch (V4L4JException e) { - throw new WebcamException(String.format("Cannot instantiate V4L4J device from %s", vfile), e); + throw new WebcamException("Cannot instantiate V4L4J device from " + file, e); } + } + + private static DeviceInfo getVideoDeviceInfo(VideoDevice device) { + LOG.trace("Get video device info"); + + DeviceInfo info = null; try { - di = vd.getDeviceInfo(); + info = device.getDeviceInfo(); } catch (V4L4JException e) { - throw new WebcamException(String.format("Cannot get V4L4J device info from %s", vfile), e); + throw new WebcamException("Cannot get V4L4J device info from " + device, e); } - ifl = di.getFormatList(); - formats = ifl.getYUVEncodableFormats(); + if (info == null) { + throw new WebcamException("Cannot get device info from device"); + } + + return info; + } + + private static ImageFormat getVideoBestImageFormat(VideoDevice device) { + + if (device == null) { + throw new IllegalArgumentException("Device must not be null!"); + } + + ImageFormatList formatsList = getVideoDeviceInfo(device).getFormatList(); + List formats = formatsList.getJPEGEncodableFormats(); + + int min = Integer.MAX_VALUE; + ImageFormat bestFormat = null; for (ImageFormat format : formats) { + ResolutionInfo info = format.getResolutionInfo(); + ResolutionInfo.Type type = info.getType(); String name = format.getName(); - LOG.debug("Found format {}", name); - if (name.startsWith("YU")) { + // skip unsupported resolution type + + switch (type) { + case UNSUPPORTED: + LOG.trace("Ignore {}, resolution type not supported", name); + continue; + case DISCRETE: + case STEPWISE: + break; + default: + throw new WebcamException("Unknown resolution type " + type); + } - ResolutionInfo ri = format.getResolutionInfo(); - LOG.debug("Resolution info {} {}", name, ri); + LOG.trace("Testing {}", name); - for (DiscreteResolution dr : ri.getDiscreteResolutions()) { - resolutions.add(new Dimension(dr.getWidth(), dr.getHeight())); + for (int i = 0; i < BEST_FORMATS.length; i++) { + if (name.startsWith(BEST_FORMATS[i]) && i < min) { + min = i; + bestFormat = format; } } } + + LOG.debug("Best image format match {}", bestFormat); + + if (bestFormat == null) { + throw new WebcamException("No suitable image format detected"); + } + + return bestFormat; + } + + private static List getResolutionsDiscrete(ResolutionInfo info) { + List resolutions = new ArrayList(); + for (DiscreteResolution resolution : info.getDiscreteResolutions()) { + resolutions.add(new Dimension(resolution.getWidth(), resolution.getHeight())); + } + return resolutions; + } + + private static List getResolutionsStepwise(ResolutionInfo info) { + + List resolutions = new ArrayList(); + StepwiseResolution resolution = info.getStepwiseResolution(); + + int minW = resolution.getMinWidth(); + int minH = resolution.getMinHeight(); + int maxW = resolution.getMaxWidth(); + int maxH = resolution.getMaxHeight(); + int stepW = resolution.getWidthStep(); + int stepH = resolution.getHeightStep(); + + for (WebcamResolution r : WebcamResolution.values()) { + + Dimension size = r.getSize(); + + int w = size.width; + int h = size.height; + + boolean wok = w <= maxW && w >= minW; + boolean hok = h <= maxH && h >= minH; + boolean sok = w % stepW == 0 && h % stepH == 0; + + if (wok && hok && sok) { + resolutions.add(size); + } + } + + return resolutions; + } + + /** + * Get video resolutions from {@link ImageFormat}. + * + * @param format the {@link ImageFormat} to test + * @return List of resolutions supported by given format + */ + private static List getVideoResolutions(ImageFormat format) { + + if (format == null) { + throw new IllegalArgumentException("Image format cannot be null!"); + } + + ResolutionInfo info = format.getResolutionInfo(); + ResolutionInfo.Type type = info.getType(); + + switch (type) { + case DISCRETE: + return getResolutionsDiscrete(info); + case STEPWISE: + return getResolutionsStepwise(info); + case UNSUPPORTED: + default: + throw new WebcamException("Unsupported resolution type " + type); + } } @Override public String getName() { - return vfile.getAbsolutePath(); + return videoFile.getAbsolutePath(); } @Override public Dimension[] getResolutions() { - return resolutions.toArray(new Dimension[resolutions.size()]); + return videoResolutions.toArray(new Dimension[videoResolutions.size()]); } @Override public Dimension getResolution() { if (resolution == null) { - if (resolutions.isEmpty()) { - throw new WebcamException("No valid resolution detected for " + vfile); + if (videoResolutions.isEmpty()) { + throw new WebcamException("No valid resolution detected for " + videoFile); } - resolution = resolutions.get(0); + resolution = videoResolutions.get(0); } return resolution; } @@ -153,14 +299,18 @@ public synchronized void open() { return; } - LOG.debug("Opening V4L4J device {}", vfile); + if (!videoDevice.supportJPEGConversion()) { + throw new WebcamException("Video device does not support JPEG conversion"); + } + + LOG.debug("Opening V4L4J device {}", videoFile); Dimension d = getResolution(); LOG.debug("Constructing V4L4J frame grabber"); try { - grabber = vd.getJPEGFrameGrabber(d.width, d.height, 0, 0, 80); + grabber = videoDevice.getJPEGFrameGrabber(d.width, d.height, 0, 0, 80, videoBestImageFormat); } catch (V4L4JException e) { throw new WebcamException(e); } @@ -195,7 +345,7 @@ public synchronized void close() { return; } - LOG.debug("Closing V4L4J device {}", vfile); + LOG.debug("Closing V4L4J device {}", videoFile); try { grabber.stopCapture(); @@ -207,9 +357,9 @@ public synchronized void close() { } grabber = null; - vd.releaseFrameGrabber(); + videoDevice.releaseFrameGrabber(); - LOG.debug("V4L4J device {} has been closed", vfile); + LOG.debug("V4L4J device {} has been closed", videoFile); } @@ -220,16 +370,16 @@ public void dispose() { return; } - LOG.debug("Disposing V4L4J device {}", vfile); + LOG.debug("Disposing V4L4J device {}", videoFile); if (open.get()) { close(); } - vd.releaseControlList(); - vd.release(); + videoDevice.releaseControlList(); + videoDevice.release(); - LOG.debug("V4L4J device {} has been disposed", vfile); + LOG.debug("V4L4J device {} has been disposed", videoFile); } @Override diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamResolution.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamResolution.java index b42ecce4..43296405 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamResolution.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamResolution.java @@ -73,7 +73,19 @@ public enum WebcamResolution { /** * Size 2048x1536 */ - QXGA(2048, 1536); + QXGA(2048, 1536), + + /** + * Size 2560x1440 + */ + WQHD(2560, 1440), + + /** + * Size 2560x1600 + */ + WQXGA(2560, 1600), + + ; /** * Resolution size.