Socket Bildübertragung

tobi193

Aktives Mitglied
Hallo,

ich möchte eine Anwendung schreiben, die im LAN oder Internet einen Webcamstream ermöglicht. Da ich mit Videostreams bisher nur auf der Stelle getreten bin, habe ich mich erstmal dazu entschlossen einzelne Bilder zu übertragen. Das klappt auch ganz (damit: http://www.java-forum.org/netzwerkprogrammierung/99493-imageicon-ueber-socket-fehler.html).
Zudem ist das praktisch um z.B. die Framerate einzustellen um Traffic zu sparen (da es später auf einem Netbook mit Surfstick laufen soll).
Das funktioniert auch ganz gut, aber:
- Mit der Zeit baut sich eine wahnsinns Verzögerung auf (im Internet nach 2-3min sind es 20 Sekunden, im LAN auch > 5
Sekunden)
- Anzahl an Frames ist extrem gering (im LAN ca. 2 FPS maximal, im Internet 1/20)

Mit der Wartezeit in
Java:
Thread.sleep(x);
habe ich schon herumexperimentiert. Das ändert jedoch nichts.
Die Internetleitung sollte schnell genug sein, MSN und co. laufen im Webcamchat ja auch prima.

Der Quellcode (Webcamauslesen läuft mit DSJ):

Sender:
Java:
           socket = new ServerSocket(2000);
            Socket so = socket.accept();

            ObjectOutputStream oos = new ObjectOutputStream(so.getOutputStream());

            ImageIcon img;
            while(true){
                try{
                    img = new ImageIcon(webcam.getImage());
                    oos.writeObject(img);
                    oos.reset();
                    oos.flush();
                    Thread.sleep(500);
                }catch(Exception e){}
            }

Empfänger:
Java:
                Socket socket = new Socket("********", 2000);
                ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

                ImageIcon imageIcon;
                while((imageIcon = (ImageIcon) in.readObject()) != null){
                    image = imageIcon.getImage();
                    repaint();
                }
            
                in.close();
                socket.close();
 

kay73

Bekanntes Mitglied
Die Internetleitung sollte schnell genug sein, MSN und co. laufen im Webcamchat ja auch prima.
Ich will ja Deinen Elan nicht bremsen, aber Dir sollte klar sein, dass Du Deine Resultate nicht mit den Implementierungen kommerzieller Hersteller vergleichen kannst. Da kommen optimierte Kompressions- und Bildkodiertechniken zum Einsatz, die sich in Java kaum effizient nachbauen lassen. Auch SUN hat sein Java Media Framework aufs Altenteil geschickt.

Alleine ganze Objekte in Echtzeit zu (de-) serialisieren und zu verschicken ist schon der totale Overkill und mich wundert, dass man da von Frameraten sprechen kann.
 
Zuletzt bearbeitet:

tobi193

Aktives Mitglied
Und was wäre die Alternative ?
Wenn ich auf einen Bruchteil daran herankommen könnte wäre das schon super. Aber aktuell ist das kein Zustand.
 

kay73

Bekanntes Mitglied
Die Frage ist, was Dir als Anwendungsfall vorschwebt. Du wirst in "nativem" Java keinesfalls einen Skype/Facetime/Yahoo! Stream mit 640x480 Auflösung in 30 fps umsetzen können. Ein paar wackelige Bilder im Webbrowser sind sicherlich kein Problem.

Wie zeigst Du denn die Bilder an?

Ich kenne mich leider mangels Windows nicht mit DShow aus, aber der Wrapper hat doch allerlei Sinks vorkonfiguriert. Damit sollte es doch gehen, zumindest ein Stream effizient zu verschicken,
 
Zuletzt bearbeitet:

tobi193

Aktives Mitglied
Also konkret geht es um eine 320x240 Webcam. 15 fps wären auch noch ok.
Dargestellt wird es in einem anderem Fenster mittels Graphics auf einem JPanel (nur bei einem Client (wird auch definitiv immer nur einer sein)). Es geht auch nur in eine Richtung, der Server (mit der Webcam) bekommt kein Bild vom Client.

Ja, das geht mit DSJ. Aber dafür sind die Tutorials sehr rar und nur mit JavaDoc ist das schwierig.
 

kay73

Bekanntes Mitglied
DSJ hat doch direkt eine MPJG Sink; das sollte doch schon deutlich flotter sein; alleine deshalb weil die Kodierung wahrscheinlich schon nativ erfolgt.

Alternativ Ich würde mal folgendes Experiment machen: Frames grabben, mittels ImageIO nach JPEG encoden und die Bytes als MPJG an einen Browser schicken, z. B. eben als Motion JPEG ? Wikipedia.

Ich kann Dir aber bei DSJ nicht helfen; ich hab hier nur v4l.
 
Zuletzt bearbeitet:

tobi193

Aktives Mitglied
Wie weit hat man denn bei einem "echten" Videostream einfluss auf die Frames die gesendet werden ?
Ich möchte ja die Option haben die ständig ändern zu können. Wenn was passiert sollen die zwischenzeitlich hochgedreht werden.

Wie genau meinst du das mit "an einen Browser schicken" ?
Die Übertragung per ImageIO klappt prima, aber halt nur mit einem Bild.
 

kay73

Bekanntes Mitglied
Wie weit hat man denn bei einem "echten" Videostream einfluss auf die Frames die gesendet werden ?
Wenn es ein "Film" (z. B. MPEG Stream usw.) ist, wahrscheinlich keinen. Aber Du hast ja ein anderes Anwendungsgebiet.
Wenn was passiert sollen die zwischenzeitlich hochgedreht werden.
So in Richtung Bewegungssensor/"Alarmanlage"? Jetzt kapier ich. Ok, andere Baustelle.
Wie genau meinst du das mit "an einen Browser schicken" ?
Motion-JPEG als Server-Push. Scheint sehr einfach, aber das wollte bei mir nicht soooo recht.
[Chapter 6] 6.6 Animation
JPEG Cameras
http://www.abiglime.com/webmaster/articles/cgi/032498.htm
 

Marco13

Top Contributor
Es gibt da ein paar potentiell bremsende Faktoren. Natürlich erstmal die Netzwerkverbindung, aber auch ggf. das encodieren/decodieren von Bildern: Wenn man Traffic sparen will, komprimiert man die Bilder natürlich, und das kann teuer sein. Jedenfalls habe ich mal so eine Anwendung geschrieben (eher eine Bibliothek), bei der Bilder übers Netz übertragen werden, und die konnten dann z.B. als PNG oder JPG komprimiert verschickt werden (was besser ist hängt vom Bildinhalt ab).

Diese Schleife, die da alle 500ms ein Bild rausschickt, sieht sehr suspekt aus. Ich bin kein Netzwerkexperte, aber soweit ich weiß gibt es ein Problem, wenn man vom Programm aus Daten schneller rausschickt, als die Netzwerkleitung es verkraftet - zumindest hatte ich mit diesem "naiven" Ansatz den Effekt, dass der Client teilweise Dinge gesehen hat, die 10 Sekunden vorher auf dem Server passiert sind. Irgendwann wird da wohl irgendein Puffer platzen.

Letztendlich lief das in dieser Bibliothek (die auch für mehrere Clients gedacht war, und auch sonst noch ein paar weitergehende Funktionen haben sollte) darauf hinaus, dass Client und Server sich "unterhalten" mußten... dazu hatte ich aber unter http://www.java-forum.org/awt-swing-swt/99632-bildqualitaet-verringern.html#post634006 schon mal was geschrieben.
 

kay73

Bekanntes Mitglied
Ich habe hier mal rumgespielt und einen validen single client Server-Push MJPG-Stream erzeugt, der sogar flüssig läuft. Einfach starten und und den Browser nach "http://localhost:8889" schicken.
Java:
package mjpgstream;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import javax.imageio.ImageIO;

public class Main {

    static final int W = 320;
    static final int H = 240;
    static final int D = 30;
    static final String boundary = "myboundary";
    static final byte[] boundaryBytes = ("\r\n--" + boundary + "\r\n").getBytes();
    static final byte[] contentTypeImageJPEG = "Content-Type: image/jpeg\r\n".getBytes();

    public static void main(String[] args) throws IOException, InterruptedException {

        ServerSocket ssock = null;
        try {
            ssock = new ServerSocket(8889);

            final Socket sock = ssock.accept();
            final OutputStream os = sock.getOutputStream();

            os.write(("HTTP/1.1 200 OK\r\n"
                    + "Expires: 0\r\n"
                    + "Pragma: no-cache\r\n"
                    + "Cache-Control: no-cache\r\n"
                    + "Content-Type: multipart/x-mixed-replace;boundary=" + boundary + "\r\n").getBytes());

            final BufferedImage img = new BufferedImage(W, H, BufferedImage.TYPE_3BYTE_BGR);
            final Graphics2D g = img.createGraphics();
            final ByteArrayOutputStream bos = new ByteArrayOutputStream();

            final Font font = new Font("Arial", Font.BOLD, 21);
            g.setFont(font);

            int x = D, y = D, inc_x = 1, inc_y = 1;

            for (;;) {
                g.setColor(Color.red);
                g.fillRect(0, 0, W, H);

                g.setColor(Color.WHITE);
                g.fillRect(x, y, D, D);

                if (x > W - D || x < 0) {
                    inc_x *= -1;
                }
                if (y > H - D || y < 0) {
                    inc_y *= -1;
                }

                x += inc_x;
                y += inc_y;

                g.setColor(Color.GREEN);
                final String current = System.currentTimeMillis() + "";
                g.drawString(current, 20, 20);

                ImageIO.write(img, "jpeg", bos);

                os.write(boundaryBytes);
                os.write(contentTypeImageJPEG);

                final byte[] jpg = bos.toByteArray();
                os.write(("Content-Length: " + jpg.length + "\r\n\r\n").getBytes());
                os.write(jpg);
                os.flush();
                bos.reset();

                Thread.sleep(50);
//                os.write(("--"+boundary+"--").getBytes());
            }
        } finally {
            if (ssock != null) {
                ssock.close();
            }
        }
    }
}
Geht mit Firefox 3, Google Chrome und Safari mobile. Geht nicht mit Opera 11.0, vielleicht weil der Request nicht konsumiert wird. Außerdem scheint es unbedingt nötig zu sein, einen
Code:
Thread.sleep()
zu machen;
Code:
Thread.yield()
reicht nicht. Wahrscheinlich grundsätzlich ein Problem, daß der Hack hier single-threaded ist.
 
Zuletzt bearbeitet:

homer65

Top Contributor
Ich benutze auch öfters den ObjectOutputstream um Daten per TCP/IP zu verschicken. So langsam ist das gar nicht.
Das einzige was ich in dem Quellcode nicht kenne ist:
Code:
oos.reset();
Das kenne ich nicht. Wofür ist das gut? was bewirkt es?
Bestenfalls ist es wohl überflüssig.
 

homer65

Top Contributor
Auch auf die Gefahr hin den Alleinunterhalter zu spielen. :)
Hast du dir schon mal Gedanken gemacht wie groß ein einzelnes Bild ist?
Ist die volle Auflösung notwendig? Oder kann man das Bild vorher auch auf eine geringere Größe skalieren?
Ist das Bild in einem komprimierten Format wie z.B. JPG?
Gruß Christian
 

tobi193

Aktives Mitglied
Der reset muss sein. Ohne ihn kommt ganz schnell (nach ca. 20 Sekunden) nen Fehler dass der Speicher voll ist.

Ich habe mit Kays Lösung jetzt mal ein bisschen rumprobiert. Das läuft schon besser. Man bekommt beim Webcambild rund 2-3fps bei 320x240 und die Verzögerung ist gerade so akzeptabel.

Allerdings gibt es hier auch das Problem, welches Marco13 angesprochen hat. Der Sender schickt mehr raus als empfangend wird.
Also das Gelbe vom Ei ist es noch nicht.

Ich habe jetzt mal folgendes probiert:
Java:
            OutputStream os = so.getOutputStream();
            ByteArrayOutputStream bos;

            try{
                while(true){
                    bos = new ByteArrayOutputStream();
                    ImageIO.write(webcam.getImage(), "jpeg", bos);
                    os.write(bos.toByteArray());

                    os.write(Integer.MAX_VALUE);
                    os.flush();
                    bos.reset();
                    Thread.sleep(500);
                }
            }catch(Exception e){
                System.out.println(e.getMessage());
            }
Das Integer.MAX_VALUE soll ein Trennzeichen sein um die einzelnen Bilder auseinander zu halten.
Aber beim Empfangen klappts nicht so richtig. Mit ImageIO müsste man ja immer eine neue Verbindung aufbauen um mehrere Bilder zu empfangen. Oder wie läuft das mit dem Trennzeichen ?

@Homer: 320x240, komprimierung mittels ImageIO auf JPG (aber beim ObjectStream war das noch nicht)
 
Zuletzt bearbeitet:
G

Gast2

Gast
Ich habe mit Kays Lösung jetzt mal ein bisschen rumprobiert. Das läuft schon besser. Man bekommt beim Webcambild rund 2-3fps bei 320x240 und die Verzögerung ist gerade so akzeptabel.

Allerdings gibt es hier auch das Problem, welches Marco13 angesprochen hat. Der Sender schickt mehr raus als empfangend wird.
generell sollte der Client sagen wann er ein Bild haben will ... dann liefert ihm der Server ein aktuelles ... wenn Du es als Video haben willst, wäre eine entsprechende Kodierung von Vorteil - da hier unnötiges weggeschmissen wird ... das reduziert dann auch den Traffic ... alternativ kannst Du einfach mal berechnen was Du an Daten schicken willst und mit dem vergleichen was das Netzwerk aushält ... im übrigens sind in einem 100MBit Netzwerk nicht unbedingt 100MBit verfügbar

Also das Gelbe vom Ei ist es noch nicht.
schon mal daran gedacht, das es mit Deinen technischen Mitteln - die Du verwenden willst - einfach nicht geht in der Qualität die Du haben willst ?!

hand, mogel
 

tobi193

Aktives Mitglied
Ich habe mir jetzt nochmal den Link von Marco (http://www.java-forum.org/awt-swing-swt/99632-bildqualitaet-verringern-3.html) genauer angeguckt und umgesetzt. Allerdings bekomme ich die Fehlermeldung
Code:
java.io.OptionalDataException
in der Zeile
Code:
byte[] data = (byte[]) ois.readObject();
. Eigentlich kein Wunder, schließlich soll über ein ObjectStream ein Byte-Array verschickt werden. Aber wie soll es denn sonst gehen ?

Gesendet wird so:
Java:
bos = new ByteArrayOutputStream();
ImageIO.write(webcam.getImage(), "png", bos);
oos.write(bos.toByteArray());
 

tobi193

Aktives Mitglied
Ok, ich weiß zwar nicht woran es liegt, aber plötzlich gehts.
Es sind nun alle Thread.sleep() raus und der Empfänger sendet permanent Anfragen. Allerdings gibts trotzdem nur 2fps im LAN und 1fps im Internet (gemessen auf beiden Seiten, ist immer identisch). Meine Fritzbox hat mir verraten, dass der Trafficverbrauch bei rund 16kb/s liegt. Also ist ein Bild 16kb groß bei 320x240px und JPEG Kompression.
Aber warum nur 1fps ? Die Auslastung ist auch nicht sonderlich groß.
 

Marco13

Top Contributor
Heißt "permanent", dass er nicht wartet, bis er das Bild empfangen hat? Ggf. mal mehr Code posten (ggf. auch ein KSKB, das ohne Webcam auskommt...)
 

kay73

Bekanntes Mitglied
Hm, eigentlich ganz lustig das Ganze.

Komisch, dass das alles bei Euch so langsam ist. Ich habe den Server etwas abstrahiert und die Framerzeugung mit 100 FPS konfiguriert, die on-the-fly JPEG konvertiert werden. Im Server schicke ich 4 byte voran in denen die Länge des JPEGs steht.

In dem kleinen Client (localhost) laufen 640x480 Pixel Vollbilder mit 24 fps und um die 700 kB/sec Durchsatz, 320x240 Pixel laufen so mit 70 fps. Core2Duo auf 1GHz heruntergetaktet.

Du musst ein Bild größer 640x480 in den Classpath kopieren und den Dateinamen in den Code ("your picture here!") einfügen. Ansonsten sollte beides out-of-the-box laufen.

Java:
package mjpgstream;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;

class Util {

    public static final void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException ex) {
            Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

class FrameProducer implements Runnable {

    static final int W = 640;
    static final int H = 480;
    static final int D = 30;
    private final BufferedImage buffer = new BufferedImage(W, H, BufferedImage.TYPE_3BYTE_BGR);
    private final Image background;
    private static final Logger logger = Logger.getLogger(FrameProducer.class.getName());
    private final ImageWriter imageWriter;
   
    public FrameProducer() {
        Image img = null;
        ImageWriter wr = null;
        try {
            // your picture here!
            img = ImageIO.read(getClass().getClassLoader().getResourceAsStream("replace me!.jpg"));
            wr = ImageIO.getImageWritersByFormatName("JPEG").next();
        } catch (Exception ex) {
            logger.severe(ex.getMessage());
        } finally {
            background = img;
            imageWriter = wr;
        }
    }

    public void run() {

        logger.info("producing frames...");
        int x = D, y = D, inc_x = 1, inc_y = 1, ix = 50, iy = 05, inc_ix = 1, inc_iy = 1;

        final Font font = new Font("Arial", Font.BOLD, 21);

        final Graphics2D g = buffer.createGraphics();
        g.setFont(font);

        final int backgroundW = background.getWidth(null) ;
        final int backgroundH = background.getHeight(null);
        
        int nFrames = 0;
        long start = System.currentTimeMillis();

        for (;;) {

            synchronized (buffer) {

                g.drawImage(background, 0, 0, W, H, ix, iy, ix + W, iy + H, null);
                if (ix > backgroundW - W || ix < 0) {
                    inc_ix *= -1;
                }
                if (iy > backgroundH - H || iy < 0) {
                    inc_iy *= -1;
                }
                ix += inc_ix;
                iy += inc_iy;
                g.setColor(Color.WHITE);
                g.fillRect(x, y, D, D);
                if (x > W - D || x < 0) {
                    inc_x *= -1;
                }
                if (y > H - D || y < 0) {
                    inc_y *= -1;
                }
                x += inc_x;
                y += inc_y;
                g.setColor(Color.GREEN);
                final String current = System.currentTimeMillis() + "";
                g.drawString(current, 20, 20);
            }

            Util.sleep(10);

            nFrames++;
            final long now = System.currentTimeMillis();
            final long delta = now - start;
            if (delta > 2000L) {
                float fps = ((float) nFrames) / delta * 1000.0f;
                logger.info(String.format("%1$2.3fFPS", fps));

                start = System.currentTimeMillis();
                nFrames = 0;
            }
        }
    }

    public RenderedImage grabFrame() {
        final BufferedImage frame;
        synchronized (buffer) {
            frame = new BufferedImage(buffer.getWidth(),
                    buffer.getHeight(), buffer.getType());
            frame.setData(buffer.getData());
        }
        return frame;
    }

    public byte[] grabJPEGFrame() throws IOException {
        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(grabFrame(), "jpeg", bos);
        return bos.toByteArray();
    }
}

abstract class Server implements Runnable {

    private final int port;
    private static final ExecutorService EXS = Executors.newCachedThreadPool();
    private static final Logger logger = Logger.getLogger(Server.class.getName());

    public Server(int port) {
        this.port = port;
    }

    abstract Object doServe(final Socket socket) throws Exception;
    
    public void run() {
        ServerSocket ssock = null;
        try {
            try {
                ssock = new ServerSocket(port);

                for (;;) {
                    final Socket sock = ssock.accept();
                    logger.info("accept: " + sock.getInetAddress());
                    EXS.submit(new Callable<Object>() {

                        public Object call() throws Exception {
                            return doServe(sock);
                        }
                    });
                }
            } finally {
                if (ssock != null) {
                    ssock.close();
                }
            }
        } catch (final Throwable t) {
            throw new RuntimeException(t);
        }
    }
}

class MJPGServer extends Server {

    private static final String boundary = "myboundary";
    private static final byte[] boundaryBytes = ("\r\n--" + boundary + "\r\n").getBytes();
    private static final byte[] contentTypeImageJPEG = "Content-Type: image/jpeg\r\n".getBytes();
    private static final Logger logger = Logger.getLogger(MJPGServer.class.getName());
    private static final ExecutorService EXS = Executors.newCachedThreadPool();
    private final FrameProducer frameProducer;

    public MJPGServer(final FrameProducer frameProducer, int port) {
        super(port);
        this.frameProducer = frameProducer;
    }

    @Override
    Object doServe(Socket socket) throws Exception {
        final OutputStream os = socket.getOutputStream();

        os.write(("HTTP/1.1 200 OK\r\n"
                + "Expires: 0\r\n"
                + "Pragma: no-cache\r\n"
                + "Cache-Control: no-cache\r\n"
                + "Content-Type: multipart/x-mixed-replace;boundary=" + boundary + "\r\n").getBytes());

        for (;;) {
            try {
                os.write(boundaryBytes);
                os.write(contentTypeImageJPEG);

                final byte[] jpg = frameProducer.grabJPEGFrame();
                os.write(("Content-Length: " + jpg.length + "\r\n\r\n").getBytes());
                os.write(jpg);
                os.flush();
            } catch (final Exception ex) {
                logger.severe(ex.getMessage());
                break;
            }
            Util.sleep(25);
        }
        logger.info("exit: " + socket.getInetAddress());
        return null;
    }
}

class RawFrameServer extends Server {

    private final FrameProducer frameProducer;

    public RawFrameServer(final FrameProducer frameProducer, int port) {
        super(port);
        this.frameProducer = frameProducer;
    }

    @Override
    Object doServe(Socket socket) throws Exception {
        final OutputStream os = socket.getOutputStream();
        final byte buffer [] = new byte [4];
        for(;;) {
            final byte [] frame = frameProducer.grabJPEGFrame();

            final int l = frame.length;
            buffer[0] = (byte) (l & 0xff);
            buffer[1] = (byte) ((l >> 8) & 0xff);
            buffer[2] = (byte) ((l >> 16) & 0xff);
            buffer[3] = (byte) ((l >> 24) & 0xff);

            os.write(buffer ,0 ,4);
            os.write(frame);
            os.flush();
        }
    }
}

public class ServerRunner {

    private static final Logger logger = Logger.getLogger(ServerRunner.class.getName());
    static final FrameProducer frameProducer = new FrameProducer();
    static final MJPGServer MJPGServer = new MJPGServer(frameProducer, 8889);
    static final RawFrameServer rawFrameServer = new RawFrameServer(frameProducer, 8888);
    static final ExecutorService EXS = Executors.newCachedThreadPool();

    public static void main(String[] args) throws IOException, InterruptedException {

        EXS.submit(frameProducer);
        EXS.submit(MJPGServer);
        EXS.submit(rawFrameServer);
    }
}
Java:
package mjpgstream;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Image;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class RawFrameClient extends JFrame implements Runnable {

    final JPanel picPanel = new JPanel();
    private static final Logger logger = Logger.getLogger(RawFrameClient.class.getName());

    public RawFrameClient() {
        final Container cnt = getContentPane();
        cnt.setLayout(new BorderLayout());

        picPanel.setBackground(Color.GREEN);
        cnt.add(picPanel, BorderLayout.CENTER);
    }

    public void readBytes(final InputStream is, final byte[] buffer, int l) throws IOException {
        int left = l;
        int read = 0;

        while (left > 0) {
            int n = is.read(buffer, read, left);
            left -= n;
            read += n;
        }
    }

    public void run() {
        try {
            final Socket sock = new Socket("127.0.0.1", 8888);
            final InputStream is = sock.getInputStream();
            final byte[] buffer = new byte[4];

            int nFrames = 0;
            int readBytes = 0;
            long start = System.currentTimeMillis();

            for (;;) {
                readBytes(is, buffer, 4);
                readBytes += 4;

                int size = 0;
                size += buffer[0] & 0xff;
                size += (buffer[1] << 8) & 0xff00;
                size += (buffer[2] << 16) & 0xff0000;
                size += (buffer[3] << 24) & 0xff000000;

                final byte[] jpeg_frame = new byte[size];
                readBytes(is, jpeg_frame, size);

                final Image img = ImageIO.read(new ByteArrayInputStream(jpeg_frame));
                picPanel.getGraphics().drawImage(img, 0, 0, null);

                nFrames++;
                readBytes += size;
                
                final long now = System.currentTimeMillis();
                final long delta = now - start;
                if (delta > 2000L) {
                    final float d = ((float)delta) / 1000.0f;
                    float fps = ((float) nFrames) / d;
                    logger.info(String.format("%1$2.3fFPS, %2$2.3f kB/sec", fps,
                    ((float)readBytes / d) / 1024.0f));

                    start = System.currentTimeMillis();
                    nFrames = 0;
                    readBytes=0;
                }
            }
        } catch (final Throwable t) {
        }
    }

    public static void main(final String[] args) {
        final RawFrameClient c = new RawFrameClient();
        c.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Executors.newSingleThreadExecutor().submit(c);

        c.setSize(800, 800);
        c.setVisible(true);
    }
}
 
Zuletzt bearbeitet:

tobi193

Aktives Mitglied
Ich habe jetzt nochmal weiter experimentiert. Es läuft so halbwegs. Die Verzögerung ist nun weg und die Framerate hat sich etwas erhöht. Aber richtig gut ist es nach wie vor nicht. Ich habe mal die verschiedenen Schritte genau unter die Lupe genommen und dabei ist mir aufgefallen, dass das Holen des Images von der Webcam sehr lange dauert (400ms).
Wahrscheinlich gibts da noch was besseres als DSJ.

So sieht jetzt der Sender aus:
Java:
try{
            socket = new ServerSocket(2000);
            Socket so = socket.accept();

            BufferedReader ois = new BufferedReader(new InputStreamReader(so.getInputStream()));
            ObjectOutputStream oos = new ObjectOutputStream(so.getOutputStream());
            ByteArrayOutputStream bos = null;

            while(true){
                String s = ois.readLine();

                if(s.equals("-4")){

                    bos = new ByteArrayOutputStream();
                    ImageIO.write(webcam.getImage(), "jpeg", bos);

                    oos.writeObject(bos.toByteArray());

                    oos.flush();
                    bos.reset();
                    
                }
            }

        }catch(Exception e){
            e.printStackTrace();
        }

Der Empfänger:
Java:
try{
                Socket socket = new Socket("*********", 2000);

                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                BufferedWriter ops = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

                while(true){
                    long t = System.currentTimeMillis();
                    ops.write("-4", 0, 2);
                    ops.newLine();
                    ops.flush();
                    
                    byte[] data = (byte[]) ois.readObject();
                    image = ImageIO.read(new ByteArrayInputStream(data));
                    System.out.println(System.currentTimeMillis() - t);
                    update();
                }

            }catch(Exception e){
                e.printStackTrace();
            }
        }

        public void update(){
            Thread th = new Thread(){
                public void run(){
                    repaint();
                }
            };
            th.start();
        }
 
Zuletzt bearbeitet:

tobi193

Aktives Mitglied
Wie gesagt wird auf einfachst mögliche Weise mit DSJ das aktuelle Bild geholt:

Java:
import de.humatic.dsj.DSCapture;
import de.humatic.dsj.DSFilterInfo;
import java.awt.image.BufferedImage;

public class Webcam{

    private DSCapture dsc = null;

    public Webcam(){
        dsc = new DSCapture(0, DSCapture.queryDevices()[0][0], false, DSFilterInfo.doNotRender(), null);
    }

    public BufferedImage getImage(){
        return dsc.getImage();
    }

}

Allerdings braucht ein Aufruft von getImage() rund 400ms. Das halte ich doch irgendwie für relativ viel. An den Filtereinstellungen usw. habe ich schon mächtig rumgedreht, ändert aber nichts.
 

kay73

Bekanntes Mitglied
Ersetz doch die webcam-Implementierung gegen eine synthetische Bilderzeugung zum Testen und schau dann wie sich die Frameraten ändern.
 

tobi193

Aktives Mitglied
Hm, da steigt die Framerate auf 4 fps im LAN und 70-80 fps wenn ich das ganze im localhost ausführe.
Was aber bei letzterem verwundert, ist dass alle rund 5-10 Sekunden kurzzeitig die Rate ziemlich einknickt.
 

kay73

Bekanntes Mitglied
Was aber bei letzterem verwundert, ist dass alle rund 5-10 Sekunden kurzzeitig die Rate ziemlich einknickt.
Ich tippe auf zwei Ursachen:
  • Garbage-Collection, da "dank" des Neu-Erzeugens von Objekten bei (De-)Serialisierung die Objektanzahl explodiert.
  • Deine Update-Funktion startet neue Threads????? Wieso? Hat das das mit dem Swing-EventLoop zu tun? Falls, ja, dafür gibt es die
    Code:
    SwingUtils.invoke*()
    Methoden. Bei mir ging's auch ohne. Ansonsten einen Executors.newCachedThreadPool() verwenden. Threads sind ein teurer Spass.
Hau doch einfach die Framegröße als Integer und dann das JPEG-Frame in den Socket raus, das reicht doch erst mal als "Protokoll". Bei meinem Doofie-Client läuft das stabil.

Hier ist noch eine Convenience-Klasse zum Komprimieren der Frames, mit der ich die Bandbreite drastisch senken konnte. Benutzung:
Java:
        final FrameCompressor compressor = new FrameCompressor();
        compressor.setQuality(0.1f);
Und dann
Code:
compressor.getFrame(...);
Java:
package mjpgstream;

import java.awt.image.RenderedImage;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Locale;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.IIOByteBuffer;
import javax.imageio.stream.ImageOutputStream;
import org.apache.commons.io.output.ByteArrayOutputStream;

public class FrameCompressor implements ImageOutputStream {

    private final ImageWriter wr = ImageIO.getImageWritersByFormatName("JPEG").next();
    private final ImageWriteParam iwparam = new JPEGImageWriteParam(Locale.getDefault());
    private final ByteArrayOutputStream bos = new ByteArrayOutputStream();

    public FrameCompressor() {
        wr.setOutput(this);
        synchronized(iwparam) {
            iwparam.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
        }
    }

    public void setQuality(final float quality) {
        synchronized(iwparam) {
            iwparam.setCompressionQuality(quality);
        }
    }

    public byte [] getFrame(final RenderedImage img) throws IOException {
        synchronized(bos) {
            bos.reset();
            synchronized(iwparam) {
                wr.write(null, new IIOImage(img, null, null), iwparam);
            }
            return bos.toByteArray();
        }
    }
    
    public void reset() throws IOException {
        bos.reset();
    }

    public void write(int b) throws IOException {
        bos.write(b);
    }

    public void write(byte[] b) throws IOException {
        bos.write(b);
    }

    public void write(byte[] b, int off, int len) throws IOException {
        bos.write(b, off, len);
    }

    public void close() throws IOException { }

    public void flush() throws IOException { }

    public void writeBit(int bit) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeBits(long bits, int numBits) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeBoolean(boolean v) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeByte(int v) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeBytes(String s) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeChar(int v) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeChars(String s) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeChars(char[] c, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeDouble(double v) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeDoubles(double[] d, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeFloat(float v) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeFloats(float[] f, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeInt(int v) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeInts(int[] i, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeLong(long v) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeLongs(long[] l, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeShort(int v) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeShorts(short[] s, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void writeUTF(String s) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public int getBitOffset() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public ByteOrder getByteOrder() {throw new UnsupportedOperationException("Not supported yet.");    }
    public long getFlushedPosition() {throw new UnsupportedOperationException("Not supported yet.");    }
    public long getStreamPosition() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public boolean isCached() {throw new UnsupportedOperationException("Not supported yet.");    }
    public boolean isCachedFile() {throw new UnsupportedOperationException("Not supported yet.");    }
    public boolean isCachedMemory() {throw new UnsupportedOperationException("Not supported yet.");    }
    public long length() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void mark() {throw new UnsupportedOperationException("Not supported yet.");    }
    public int read() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public int read(byte[] b) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public int read(byte[] b, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public int readBit() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public long readBits(int numBits) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public boolean readBoolean() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public byte readByte() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readBytes(IIOByteBuffer buf, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public char readChar() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public double readDouble() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public float readFloat() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readFully(byte[] b, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readFully(byte[] b) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readFully(short[] s, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readFully(char[] c, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readFully(int[] i, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readFully(long[] l, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readFully(float[] f, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void readFully(double[] d, int off, int len) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public int readInt() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public String readLine() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public long readLong() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public short readShort() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public String readUTF() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public int readUnsignedByte() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public long readUnsignedInt() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public int readUnsignedShort() throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }    
    public void flushBefore(long pos) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void seek(long pos) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void setBitOffset(int bitOffset) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public void setByteOrder(ByteOrder byteOrder) {throw new UnsupportedOperationException("Not supported yet.");    }
    public int skipBytes(int n) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
    public long skipBytes(long n) throws IOException {throw new UnsupportedOperationException("Not supported yet.");    }
}
 
Zuletzt bearbeitet:

athikka

Aktives Mitglied
Ich würde dir für diesen Fall ganz klar UDP empfehlen, da kriegst du locker deine 15fps zusammen.

ich habe unlängst es erst geschafft mit udp (und jpeg kompression) den ganzen desktop (1440x900) zweimal pro sekunde zu senden.

da wird bei der viel kleineren auflösung sicher deutlich mehr zu holen sein


PS: ich empfehle dir, nicht die ImageIcon objekte zu senden, sondern mach ein bufferedimage draus, kompimiere es zb. mit ImageIO (jpg) und mach ein byte-array draus. das ist ca 10 mal kleiner als ein aufgeblasenes imageicon objekt
hab ich selbst probiert und gestaunt!
 

Neue Themen


Oben