reduce java-based cpu usage when scanning

This commit is contained in:
Craig Raw 2021-04-05 11:29:13 +02:00
parent e524396aaf
commit d635815607
3 changed files with 81 additions and 29 deletions

View file

@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.control;
import com.github.sarxos.webcam.WebcamEvent; import com.github.sarxos.webcam.WebcamEvent;
import com.github.sarxos.webcam.WebcamListener; import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamResolution; import com.github.sarxos.webcam.WebcamResolution;
import com.github.sarxos.webcam.WebcamUpdater;
import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.OutputDescriptor; import com.sparrowwallet.drongo.OutputDescriptor;
@ -39,6 +40,7 @@ import javafx.geometry.Insets;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.util.Duration;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.tools.Borders; import org.controlsfx.tools.Borders;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -67,6 +69,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
private static final Pattern PART_PATTERN = Pattern.compile("p(\\d+)of(\\d+) (.+)"); private static final Pattern PART_PATTERN = Pattern.compile("p(\\d+)of(\\d+) (.+)");
private static final int SCAN_PERIOD_MILLIS = 100;
private final ObjectProperty<WebcamResolution> webcamResolutionProperty = new SimpleObjectProperty<>(WebcamResolution.VGA); private final ObjectProperty<WebcamResolution> webcamResolutionProperty = new SimpleObjectProperty<>(WebcamResolution.VGA);
private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0); private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0);
@ -79,7 +82,9 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
webcamResolutionProperty.set(WebcamResolution.HD); webcamResolutionProperty.set(WebcamResolution.HD);
} }
this.webcamService = new WebcamService(webcamResolutionProperty.get(), new QRScanListener()); this.webcamService = new WebcamService(webcamResolutionProperty.get(), new QRScanListener(), new ScanDelayCalculator());
webcamService.setPeriod(Duration.millis(SCAN_PERIOD_MILLIS));
webcamService.setRestartOnFailure(false);
WebcamView webcamView = new WebcamView(webcamService); WebcamView webcamView = new WebcamView(webcamService);
final DialogPane dialogPane = new QRScanDialogPane(); final DialogPane dialogPane = new QRScanDialogPane();
@ -703,4 +708,10 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
super(message, cause); super(message, cause);
} }
} }
public static class ScanDelayCalculator implements WebcamUpdater.DelayCalculator {
public long calculateDelay(long snapshotDuration, double deviceFps) {
return Math.max(SCAN_PERIOD_MILLIS - snapshotDuration, 0L);
}
}
} }

View file

@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.control;
import com.github.sarxos.webcam.Webcam; import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamListener; import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamResolution; import com.github.sarxos.webcam.WebcamResolution;
import com.github.sarxos.webcam.WebcamUpdater;
import com.google.zxing.*; import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
@ -10,7 +11,7 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Service; import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.embed.swing.SwingFXUtils; import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image; import javafx.scene.image.Image;
@ -19,16 +20,24 @@ import java.awt.image.BufferedImage;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class WebcamService extends Service<Image> { public class WebcamService extends ScheduledService<Image> {
private WebcamResolution resolution; private WebcamResolution resolution;
private final WebcamListener listener; private final WebcamListener listener;
private BooleanProperty opening = new SimpleBooleanProperty(false); private final WebcamUpdater.DelayCalculator delayCalculator;
private final BooleanProperty opening = new SimpleBooleanProperty(false);
private final ObjectProperty<Result> resultProperty = new SimpleObjectProperty<>(null); private final ObjectProperty<Result> resultProperty = new SimpleObjectProperty<>(null);
public WebcamService(WebcamResolution resolution, WebcamListener listener) { private static final int QR_SAMPLE_PERIOD_MILLIS = 400;
private Webcam cam;
private long lastQrSampleTime;
public WebcamService(WebcamResolution resolution, WebcamListener listener, WebcamUpdater.DelayCalculator delayCalculator) {
this.resolution = resolution; this.resolution = resolution;
this.listener = listener; this.listener = listener;
this.delayCalculator = delayCalculator;
this.lastQrSampleTime = System.currentTimeMillis();
} }
@Override @Override
@ -36,35 +45,52 @@ public class WebcamService extends Service<Image> {
return new Task<Image>() { return new Task<Image>() {
@Override @Override
protected Image call() throws Exception { protected Image call() throws Exception {
Webcam cam = Webcam.getWebcams(1, TimeUnit.MINUTES).get(0);
try { try {
cam.setCustomViewSizes(resolution.getSize()); if(cam == null) {
cam.setViewSize(resolution.getSize()); cam = Webcam.getWebcams(1, TimeUnit.MINUTES).get(0);
if(!Arrays.asList(cam.getWebcamListeners()).contains(listener)) { cam.setCustomViewSizes(resolution.getSize());
cam.addWebcamListener(listener); cam.setViewSize(resolution.getSize());
if(!Arrays.asList(cam.getWebcamListeners()).contains(listener)) {
cam.addWebcamListener(listener);
}
opening.set(true);
cam.open(true, delayCalculator);
opening.set(false);
} }
opening.set(true); BufferedImage bimg = cam.getImage();
cam.open(); Image image = SwingFXUtils.toFXImage(bimg, null);
opening.set(false); updateValue(image);
while(!isCancelled()) {
if(cam.isImageNew()) { if(System.currentTimeMillis() > (lastQrSampleTime + QR_SAMPLE_PERIOD_MILLIS)) {
BufferedImage bimg = cam.getImage(); readQR(bimg);
updateValue(SwingFXUtils.toFXImage(bimg, null)); lastQrSampleTime = System.currentTimeMillis();
readQR(bimg);
}
} }
return getValue();
return image;
} finally { } finally {
opening.set(false); opening.set(false);
if(!cam.close()) {
cam.close();
}
} }
} }
}; };
} }
@Override
public void reset() {
cam = null;
super.reset();
}
@Override
public boolean cancel() {
if(cam != null && !cam.close()) {
cam.close();
}
return super.cancel();
}
private void readQR(BufferedImage bufferedImage) { private void readQR(BufferedImage bufferedImage) {
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

View file

@ -1,7 +1,10 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -14,7 +17,9 @@ public class WebcamView {
private final WebcamService service; private final WebcamService service;
private final Region view; private final Region view;
private final Label statusPlaceholder ; private final Label statusPlaceholder;
private final ObjectProperty<Image> imageProperty = new SimpleObjectProperty<>(null);
public WebcamView(WebcamService service) { public WebcamView(WebcamService service) {
this.service = service ; this.service = service ;
@ -23,22 +28,32 @@ public class WebcamView {
// make the cam behave like a mirror: // make the cam behave like a mirror:
imageView.setScaleX(-1); imageView.setScaleX(-1);
service.valueProperty().addListener((observable, oldValue, newValue) -> {
if(newValue != null) {
imageProperty.set(newValue);
}
});
this.statusPlaceholder = new Label(); this.statusPlaceholder = new Label();
this.view = new Region() { this.view = new Region() {
{ {
service.stateProperty().addListener((obs, oldState, newState) -> { service.stateProperty().addListener((obs, oldState, newState) -> {
switch (newState) { switch (newState) {
case READY: case READY:
statusPlaceholder.setText("Initializing"); if(imageProperty.get() == null) {
getChildren().setAll(statusPlaceholder); statusPlaceholder.setText("Initializing");
getChildren().setAll(statusPlaceholder);
}
break ; break ;
case SCHEDULED: case SCHEDULED:
statusPlaceholder.setText("Waiting"); if(imageProperty.get() == null) {
getChildren().setAll(statusPlaceholder); statusPlaceholder.setText("Waiting");
getChildren().setAll(statusPlaceholder);
}
break ; break ;
case RUNNING: case RUNNING:
imageView.imageProperty().unbind(); imageView.imageProperty().unbind();
imageView.imageProperty().bind(service.valueProperty()); imageView.imageProperty().bind(imageProperty);
getChildren().setAll(imageView); getChildren().setAll(imageView);
break ; break ;
case CANCELLED: case CANCELLED: