sarxos / webcam-capture

The goal of this project is to allow integrated or USB-connected webcams to be accessed directly from Java. Using provided libraries users are able to read camera images and detect motion. Main project consist of several sub projects - the root one, which contains required classes, build-in webcam driver compatible with Windows, Linux and Mac OS, which can stream images as fast as your camera can serve them (up to 50 FPS). Main project can be used standalone, but user is able to replace build-in driver with different one - such as OpenIMAJ, GStreamer, V4L4j, JMF, LTI-CIVIL, FMJ, etc.
http://webcam-capture.sarxos.pl
MIT License
2.26k stars 1.11k forks source link

JavaFX -> Platform.runLater #339

Closed alkulsv closed 9 years ago

alkulsv commented 9 years ago

Hello!

Why its don't work in JavaFX? Got the Unknow Source Exception. In the samples for each grabbered Image you use new thread -> Platform.runLater(new Runnable() , I just try to do in one thread.

private BufferedImage grabbedImage;
...

Runnable tasks = new Runnable() {
 @Override
public void run() {
    while (!stopCamera) {
        try {
          if ((grabbedImage = webcam.getImage()) != null) {
                imageProperty.set(SwingFXUtils.toFXImage(grabbedImage, null));    Here is Exception!
                }
              } catch (Exception e) {e.printStackTrace();}
            }
          webcam.close();
          }
         };
 Thread th = new Thread(tasks);
th.setDaemon(true);
th.start();
sarxos commented 9 years ago

Hi @alkulsv,

You wrote:

Why its don't work in JavaFX? Got the Unknow Source Exception.

What do you mean by "Unknow Source Exception"? It's the first time I hear about such exception...

In the samples for each grabbered Image you use new thread

I cannot agree. There is only one thread created at line 229.

alkulsv commented 9 years ago

Hello!

Thank you for your replay. I mean, original sample have lines 209-112, for each grabbered image start new thread. Really original sample work. It have no problems in low resolution. But in high resolution can.t core all the trhreads, so JVM is overload.

I just try to delete Platform.runLater(new Runnable(), so don't grab new image until processing image (line 213-215). But in this case I have Exception, like "grabbedImage" is wrong Source? Can't understand.

In the debug mode I have exception in line 213: com.sun.jdi.ClassNotLoadedException: Type has not been loaded occured invoking method.

In the concole lake that:

Exception in thread "Thread-7" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-7
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Unknown Source)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(Unknown Source)
    at javafx.scene.Scene.addToDirtyList(Unknown Source)
    at javafx.scene.Node.addToSceneDirtyList(Unknown Source)
    at javafx.scene.Node.impl_markDirty(Unknown Source)
    at javafx.scene.Node.impl_geomChanged(Unknown Source)
    at javafx.scene.image.ImageView.access$300(Unknown Source)
    at javafx.scene.image.ImageView$1.invalidated(Unknown Source)
    at javafx.beans.property.ObjectPropertyBase.markInvalid(Unknown Source)
    at javafx.beans.property.ObjectPropertyBase.access$000(Unknown Source)
    at javafx.beans.property.ObjectPropertyBase$Listener.invalidated(Unknown Source)
    at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(Unknown Source)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(Unknown Source)
    at javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(Unknown Source)
    at javafx.beans.property.ObjectPropertyBase.markInvalid(Unknown Source)
    at javafx.beans.property.ObjectPropertyBase.set(Unknown Source)
    at TestJavaFX2.TestJavaFX2$2$3.run(TestJavaFX2.java:213)
    at java.lang.Thread.run(Unknown Source)

Thank you!

sarxos commented 9 years ago

@alkulsv,

The "Unknown Source" in your stack trace is not an exception. Normally you should see line number there, but since you are using JRE (I suppose) instead of JDK your Java does not have source code linked and when it comes to exceptions, it prints only method names from the stack and instead of the line number it outputs "unknown source". When you switch to JDK you should be able to see correct line numbers there.

The Platform.runLater(..) does not spawn new threads. It's a convenience method to schedule JavaFX GUI update without blocking (very similar to Swing's SwingUtilities.invokeLater(..) and the fact that you are getting IllegalStateException with "Not on FX application thread;" clearly means that you modified the code to bypass runLater(..) and update the GUI from the main thread instead of the Java FX application thread.

In regards to the above you can check the following resources:

You stated:

But in high resolution can.t core all the trhreads, so JVM is overload.

However, I'm wondering how did you confirm the JVM is overloaded? I tried JFX example on my PC and indeed, the rendering process with resolution set to HD720p was a little bit slower (I suppose on older computer is can be even slower, though), but this is not because of too many updates (or spawned threads as you supposed), but a cumulative effect of the the following:

1) image re-scaling in the runtime, 2) component paint scheduling (no real-time rendering).

In regards to 1), if I capture image with size 1280 x 720 px Java has to rescale it to fit the display (e.g. 640 x 480 px). This requires some processing time. There are some tricks to speed this up but you have to master Java Graphics2D API and know the tricks that are hidden beneath (DirectDraw/GDI optimizations), but the best solution for that (IMHO) is to keep the same size of captured image and display component. If those two match exactly, Java does not have to re-scale and can flush BufferedImage pixel data into video memory just as it is (this is pretty fast).

In regards to 2), this is due to the mechanism of how runLater(..) works. It schedules update but does not guarantee realtime - in few words - image update will be done, but this can happen in milliseconds or seconds, depending on how much work JavaFX thread needs to handle. This simply causes rendering lags. I'm not an JFX expert (I worked with Swing mostly) and cannot give you any exact recipe of how this can be workaround, but did you maybe try wrapping WebcamPanel into JavaFX component? I just guess this can work faster because WebcamPanel does not involve JavaFX thread when it's updated.

In regards to ClassNotLoadedException - this is debugger issue, not the code itself. You should not see it if you not recompile the code when application is running in debug mode. You can also try changing your IDE (e.g use NetBeans or IntelliJ instead of Eclipse).

alkulsv commented 9 years ago

Thnk you for quality answer. I recheck the project, change JRE to JDK, understund about ClassNotLoadedException and my incorrect way to modifi code bypass runLater(..). Now it is clear for me. So I really have cumulative effect. I make some image manipulation. Really I solve the problem using boolean variable, just set it true in the start process, and false in the finish. If the variable true, just skip runLater(..). I think, it is not correct way :(

sarxos commented 9 years ago

@alkulsv,

I'm not very familiar with JavaFX so it's hard to say if it's correct or not (skipping runLater()), however I crafted small example where I skipped Image conversion and embedded Swing's WebcamPanel in JavaFX application. In my opinion it works much faster then plain JavaFX ImageProperty with runLater(). You can customize it to fit your needs.

import java.awt.Dimension;

import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import javax.swing.SwingUtilities;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.WebcamResolution;

public class SwingFx extends Application {

    @Override
    public void start(Stage stage) {
        final SwingNode swingNode = new SwingNode();

        createSwingContent(swingNode);

        StackPane pane = new StackPane();
        pane.getChildren().add(swingNode);

        stage.setTitle("Test embed WebcamPanel in JFX");
        stage.setScene(new Scene(pane, 250, 150));
        stage.show();
    }

    private void createGui(final SwingNode swingNode) {

        Dimension resolution = WebcamResolution.HD720.getSize();
        Webcam webcam = Webcam.getDefault();
        webcam.setCustomViewSizes(new Dimension[] { resolution });
        webcam.setViewSize(resolution);

        WebcamPanel panel = new WebcamPanel(webcam);
        panel.start();

        swingNode.setContent(panel);
    }

    private void createSwingContent(final SwingNode swingNode) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createGui(swingNode);
            }
        });
    }

    public static void main(String[] args) {
        launch(args);
    }
}
sarxos commented 9 years ago

Hi @alkulsv, I'm closing this ticket. Please comment below if you have any further questions and/or would like to keep it open.