App zur Steuerung des mpv Mediaplayers auf einem Raspberry Pi über HTTP
ulrich
2022-02-11 b388a5decf5957cbb119b8c020baef200760d6e1
Bildbetrachter hinzugefuegt
2 files added
379 ■■■■■ changed files
src/de/uhilger/calypso/Betrachter.java 143 ●●●●● patch | view | raw | blame | history
src/org/tw/pi/framebuffer/FrameBuffer.java 236 ●●●●● patch | view | raw | blame | history
src/de/uhilger/calypso/Betrachter.java
New file
@@ -0,0 +1,143 @@
package de.uhilger.calypso;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import org.tw.pi.framebuffer.FrameBuffer;
/**
 * Klasse zum Betrachten von Fotografien auf dem Fernseher
 * mit dem Raspberry Pi
 *
 * Diese Klasse verwendet die Klasse JavaFrameBuffer
 * von Thomas Welsch, die die Ausgabe direkt in den
 * FrameBuffer des Raspberry Pi erlaubt.
 *
 * Der JavaFrameBuffer ist mit Calypso
 * enthalten und erfordert, dass beim Start die native
 * Programmbibliothek namens 'libFrameBuffer.so' eingebunden wird, die im Verteilpaket
 * von Calypso enthalten ist. Zum Einbinden der nativen Biliothek genuegt die
 * Angabe von -Djava.library.path=/pfad/zum/lib-ordner im Startskript von
 * Calypso.
 *
 * Durch die Verwendung des Framebuffer kann das Betrachten von
 * Bildern auf dem Fernseher erfolgen ohne, dass auf dem Raspberry Pi
 * eine grafische Bedienoberflaeche wie z.B. das X Windowing System,
 * XBMC oder OpenELEC, usw. laufen muss. Es genuegt Java
 * im 'headless mode', was zudem die Bedienbarkeit aus der Ferne via
 * HTTP hinzufuegt.
 *
 * @author Ulrich Hilger, https://uhilger.de
 * @author Published under the terms and conditions of
 * the <a href="http://www.gnu.org/licenses/agpl-3.0" target="_blank">GNU Affero General Public License</a>
 *
 * @version 2 vom 11. Februar 2022, Version 1 war vom 15.1.2014
 */
public class Betrachter {
  public static final long serialVersionUID = 42L;
  private static final Logger logger = Logger.getLogger(Betrachter.class.getName());
  public static final String P_FBDEV = "fbdev";
  public static final String STANDARD_FBDEV = "/dev/fb0";
  public static final String P_URL = "u";
  public static final String P_X = "x";
  public static final String P_Y = "y";
  public static final String P_WIDTH = "w";
  public static final String P_HEIGHT = "h";
  //public static final String PATH_ZEIGEN = "/zeigen";
  //public static final String PATH_LOESCHEN = "/loeschen";
  private FrameBuffer jfb;
  private BufferedImage img;
  private Graphics2D g;
  public void init(String devName) {
    String device = devName;
    if(device == null) {
      device = STANDARD_FBDEV;
    }
    jfb = new FrameBuffer(device, false);
    img = jfb.getScreen();
    g = img.createGraphics();
  }
  public void close() {
    jfb.close();
  }
  public void jfbHelloWorld() {
    g.setColor(Color.RED);
    g.drawString("Hello World !", 50, 50);
    jfb.updateScreen();
  }
  /**
   * Anzeigen eines Bildes mit Hilfe des JavaFrameBuffers
   *
   * Diese Methode laedt ein Bild via HTTP oder HTTPS und
   * schreibt es in den Framebuffer zur Anzeige
   *
   * @param urlStr  Adresse des Bildes, das angezeigt werden soll
   * @param x waagerechte Koordinate der gewuenschten Position der linken oberen Ecke des Bildes
   * @param y senkrechte Koordinate der gewuenschten Position der linken oberen Ecke des Bildes
   * @param w Breite in Bildpunkten, in der das Bild angezeigt werden soll
   * @param h Hoehe in Bildpunkten, in der das Bild angezeigt werden soll
   */
  public String jfbBildZeigen(String urlStr, int x, int y, int w, int h) {
    String antwort = "jfbBildZeigen hat nicht geklappt";
    BufferedImage webimg = null;
    try {
      /*
        vgl. http://java.kompf.de/java2d.html
      */
      //System.setProperty("sun.java2d.opengl","true");
      URL url = new URL(urlStr);
      webimg = ImageIO.read(url);
      g.drawImage(webimg, x, y, w, h, null);
      jfb.updateScreen();
      antwort = urlStr + " geladen";
    } catch (Exception e) {
      antwort = e.getMessage();
    }
    return antwort;
  }
  /**
   * Leeren des FrameBuffers
   *
   * Hier wird einfach ein Linux-Prozess eroeffnet, der das
   * Kommando dd if=/dev/zero of=/dev/fb0 ausfuehrt und
   * den Framebuffer mit Nullwerten fuellt
   */
  public String fbLeeren(String deviceName) {
    String antwort = "fbLeeren hat nicht geklappt";
    try {
      String cmd = "dd if=/dev/zero of=" + deviceName;
      Process leeren = Runtime.getRuntime().exec(cmd);
      /*MeldeThread t = new MeldeThread();
      t.setProcess(leeren);
      t.lauscherHinzufuegen(new ProzessLauscherImpl() {
        @Override
        public void prozessBeendet() {
          // etwas tun, wenn fertig...
        }
      });
      t.start();*/
      antwort = "Prozess gestartet";
    } catch(Exception ex) {
      logger.log(Level.FINE, ex.getMessage(), ex);
    }
    return antwort;
  }
}
src/org/tw/pi/framebuffer/FrameBuffer.java
New file
@@ -0,0 +1,236 @@
package org.tw.pi.framebuffer;
/*
*        This file is the JNI Java part of a Raspberry Pi FrameBuffer project.
*
*        Created 2013 by Thomas Welsch (ttww@gmx.de).
*
*        Do whatever you want to do with it :-)
*
**/
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JPanel;
/**
* This class is the Java front end for a simple to use FrameBuffer driver.
* Simple draw in the BufferedImage and all changes are transfered to the FrameBuffer device.<p>
* For testing purpose a dummy device is supported (via the devicename "dummy_160x128" instead of "/dev/fb1").<p<
* The Java process needs write access to the frame buffer device file.
* <p>
* It's used to drive small bit mapped screens connected via SPI, see
* http://www.sainsmart.com/blog/ada/
* <p>
* <p>
* My Linux kernel config for SPI display was:
* <pre>
* CONFIG_FB_ST7735=y
* CONFIG_FB_ST7735_PANEL_TYPE_RED_TAB=y
* CONFIG_FB_ST7735_RGB_ORDER_REVERSED=y
* CONFIG_FB_ST7735_MAP=y
* CONFIG_FB_ST7735_MAP_RST_GPIO=25
* CONFIG_FB_ST7735_MAP_DC_GPIO=24
* CONFIG_FB_ST7735_MAP_SPI_BUS_NUM=0
* CONFIG_FB_ST7735_MAP_SPI_BUS_CS=0
* CONFIG_FB_ST7735_MAP_SPI_BUS_SPEED=16000000
* CONFIG_FB_ST7735_MAP_SPI_BUS_MODE=0
* </pre>
* CONFIG_FB_ST7735_MAP_SPI_BUS_SPEED gives faster updates :-)
* <p>
* If you get the wrong colors, try the CONFIG_FB_ST7735_RGB_ORDER_REVERSED option !
*/
public class FrameBuffer {
        private static final int FPS = 60;                // Max. update rate
        private        String                        deviceName;
        private long                        deviceInfo;                // Private data from JNI C
        private        int                                width,height;
        private        int                                bits;
        private BufferedImage        img;
        private int[]                        imgBuffer;
        // -----------------------------------------------------------------------------------------------------------------
        private native long                openDevice(String device);
        private native void                closeDevice(long di);
        private native int                getDeviceWidth(long di);
        private native int                getDeviceHeight(long di);
        private native int                getDeviceBitsPerPixel(long di);
        private native boolean        updateDeviceBuffer(long di,int[] buffer);
        static {
                System.loadLibrary("FrameBufferJNI"); // FrameBufferJNI.dll (Windows) or FrameBufferJNI.so (Unixes)
        }
        // -----------------------------------------------------------------------------------------------------------------
        /**
         * Open the named frame buffer device and starts the automatic update thread between the internal
         * BufferedImage and the device.
         *
         * @param deviceName        e.g. /dev/fb1 or dummy_320x200
         */
        public FrameBuffer(String deviceName) {
                this(deviceName,true);
        }
        // -----------------------------------------------------------------------------------------------------------------
        /**
         * Open the named frame buffer device.
         *
         * @param deviceName        e.g. /dev/fb1 or dummy_320x200
         * @param autoUpdate        if true, starts the automatic update thread between the internal
         *                                                BufferedImage and the device.
         */
        public FrameBuffer(String deviceName, boolean autoUpdate) {
                this.deviceName = deviceName;
                deviceInfo = openDevice(deviceName);
                if (deviceInfo < 10) {
                        throw new IllegalArgumentException("Init. for frame buffer "+deviceName+" failed with error code "+deviceInfo);
                }
                this.width        = getDeviceWidth(deviceInfo);
                this.height        = getDeviceHeight(deviceInfo);
                System.err.println("Open with "+deviceName+" ("+deviceInfo+")");
                System.err.println(" width "+getDeviceWidth(deviceInfo));
                System.err.println(" height "+getDeviceHeight(deviceInfo));
                System.err.println(" bpp "+getDeviceBitsPerPixel(deviceInfo));
                // We always use ARGB image type.
                img                        = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                imgBuffer        = ((DataBufferInt) img.getRaster().getDataBuffer()).getBankData()[0];
                if (autoUpdate) new UpdateThread().start();
        }
        // -----------------------------------------------------------------------------------------------------------------
        private ScreenPanel        screenPanel;
        /**
         * Returns a JPanel which represents the actual frame buffer device.
         *
         * @return        JPanel...
         */
        public JPanel getScreenPanel() {
                synchronized (deviceName) {
                        if (screenPanel != null) throw new IllegalStateException("Only one screen panel supported");
                        screenPanel = new ScreenPanel();
                        return screenPanel;
                }
        }
        // -----------------------------------------------------------------------------------------------------------------
        /**
         * Internal helper class for displaying the current frame buffer image via a JPanel.
         */
        @SuppressWarnings("serial")
        private class ScreenPanel extends JPanel {
                public ScreenPanel() {
                        setPreferredSize(new Dimension(FrameBuffer.this.width,FrameBuffer.this.height));
                }
                @Override
                protected void paintComponent(Graphics g) {
                        super.paintComponent(g);
                        g.drawImage(img, 0, 0, null);
                }
        }
        // -----------------------------------------------------------------------------------------------------------------
        /**
         * Internal helper class for refreshing the frame buffer display and/or JPanel.
         */
        private class UpdateThread extends Thread {
                UpdateThread() {
                        setDaemon(true);
                        setName("FB "+deviceName+ " update");
                }
                @Override
                public void run() {
                        final int SLEEP_TIME = 1000 / FPS;
                        while (deviceInfo != 0) {
                                if (updateScreen()) {
                                        if (screenPanel != null) {
                                                screenPanel.repaint();
                                        }
                                }
                                try {
                                        sleep(SLEEP_TIME);
                                } catch (InterruptedException e) {
                                        break;
                                }
                        }        // while
                }
        }        // class UpdateThread
        // -----------------------------------------------------------------------------------------------------------------
        /**
         * Returns the BufferedImage for drawing. Anything your draw here is synchronizet to the frame buffer.
         *
         * @return        BufferedImage of type ARGB.
         */
        public BufferedImage getScreen() {
                return img;
        }
        // -----------------------------------------------------------------------------------------------------------------
        /**
         * Close the device.
         */
        public void close() {
                synchronized (deviceName) {
                        closeDevice(deviceInfo);
                        deviceInfo = 0;
                        img        = null;
                        imgBuffer = null;
                }
        }
        // -----------------------------------------------------------------------------------------------------------------
        /**
         * Update the screen if no automatic sync is used (see constructor autoUpdate flag).
         * This method is normally called by the autoUpdate thread.
         *
         * @return        true if the BufferedImage was changed since the last call.
         */
        public boolean updateScreen() {
                synchronized (deviceName) {
                        if (deviceInfo == 0) return false;
                        return updateDeviceBuffer(deviceInfo,imgBuffer);
                }
        }
}        // of class