Controlling a Raspberry Pi HDMI Camera with a Java API

In this post you’ll learn how you can run a Java application on a Raspberry Pi Zero 1 to turn it in a controllable HDMI camera. I use such cameras in my setup with an ATEM Mini Pro HDMI video switcher. This allows me to have four different inputs for a very affordable price to create videos, tutorials, virtual conference talks, etc. As I wanted to be able to easily change the zoom level of these Raspberry Pi cameras, I created a small Java application with an API.

Check these posts for my earlier experiments about how to use a Raspberry Pi zero as a HDMI camera:

In the following video you can find a code-walk-through and demo of this project.

Sources

You can find the sourcs of this Java application on GitHub.

Raspberry Pi board and cameras

For this project is used the following:

Thanks to the WiFi, I can access the Raspberry Pis very easily to change the software, and it also allows me to turn them into API-devices. But because they are 1’s, there are a few limitations… Let’s check what’s on board:

$ cat /proc/device-tree/model
Raspberry Pi Zero W Rev 1.1

$ uname -m
armv6l

$ uname -a
Linux af-cam-5 6.6.51+rpt-rpi-v6 #1 Raspbian 1:6.6.51-1+rpt3 (2024-10-08) armv6l GNU/Linux

$ free -h
               total        used        free      shared  buff/cache   available
Mem:           427Mi       166Mi       180Mi       1.0Mi       128Mi       261Mi
Swap:          511Mi          0B       511Mi

As you can see, my board is indeed of the first generation, which means it has an ARMv6 processor and 512Mb of memory. There are not a lot of Java runtimes for this type of board, but luckily there is still Azul Zulu 11, as I already tested in 2020. You can easily install and run it like this:

$ wget https://cdn.azul.com/zulu-embedded/bin/zulu11.76.21-ca-jdk11.0.25-linux_aarch32hf.tar.gz
$ tar -xzvf zulu11.76.21-ca-jdk11.0.25-linux_aarch32hf.tar.gz
$ rm zulu11.76.21-ca-jdk11.0.25-linux_aarch32hf.tar.gz
$ zulu11.76.21-ca-jdk11.0.25-linux_aarch32hf/bin/java -version

After these steps, you’ll get the following output:

openjdk version "11.0.25" 2024-10-15 LTS
OpenJDK Runtime Environment Zulu11.76+21-CA (build 11.0.25+9-LTS)
OpenJDK Client VM Zulu11.76+21-CA (build 11.0.25+9-LTS, mixed mode)

Perfect, we can now run any Java 11 application on this board!

Raspberry Pi as an HDMI camera

To use the Raspberry Pi as an HDMI camera, we want to have the camera overlayed in full screen, “covering” the terminal screen. Within Raspberry Pi OS, several camera commands are available. These are documented here. The one that we will be using is libcamera-hello.

We need several extra options to configure:

The documentation lists more options, but these are the only ones I use at this moment in the controller app.

Java API application

From Java, we can easily start this libcamera-hello application with the required options. In short, this is the code that starts the camera from Java:

String settings = " --viewfinder-width " + viewWidth 
  + " --viewfinder-height " + viewHeight 
  + " --roi " + zoomOffsetX + "," + zoomOffsetY 
  + "," + zoomWidth + "," + zoomHeight;
Executor.execute("libcamera-hello  " + settings + " --awb indoor -f -t 0");

By including a Jetty webserver, we can make these values configurable through an API:

public class CameraController extends HttpServlet {
 private final CameraService cameraService = CameraService.instance();

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
    try {
        if (req.getParameter("viewWidth") != null) {
            cameraService.setViewWidth(Integer.parseInt(req.getParameter("viewWidth")));
        }
        if (req.getParameter("viewHeight") != null) {
            cameraService.setViewHeight(Integer.parseInt(req.getParameter("viewHeight")));
        }
        ...
        cameraService.applySettings();
    } catch (Exception e) {
        ...
    }
  }
}

Thanks to this API, the camera can be adjusted with an API call in the following format:

http//IP_ADDRESS:8080/api/?viewWidth=1920&viewHeight=1080&zoomOffsetX=0.0&zoomOffsetY=0.0&zoomWidth=1.0&zoomHeight=1.0

Because the settings of libcamera-hello can’t be changed on the fly, the camera gets killed first, and then is restarted. This means the screen switches to terminal-view for a few seconds.

Executor.execute("killall -9 libcamera-hello");
Thread.sleep(2000);

You can build the sources with Maven and upload the jar-file to your Raspberry Pi. I used these commands to do that on my mac:

$ mvn package
$ scp target/picam-controller.jar pi@af-cam-4.local://home/pi/picam-controller/picam-controller.jar

Startup Script

Of course we want the camera to open automatically after startup of the Raspberry Pi. This can be done with a service-file with the following content. Make sure to adjust it where needed with the correct path with the Java runtime and your jar-file.

$ sudo nano /etc/systemd/system/camera.start.service

[Unit]
Description=Start the camera control app

[Service]
Type=simple
ExecStartPre=/bin/sleep 30
ExecStart=/home/pi/zulu11.76.21-ca-jdk11.0.25-linux_aarch32hf/bin/java -jar /home/pi/picam-controller/picam-controller.jar &

[Install]
WantedBy=multi-user.target

$ sudo chmod 644 /etc/systemd/system/camera.start.service
$ sudo systemctl enable camera.start.service
$ sudo systemctl start camera.start.service
$ sync
$ sudo reboot

Performance on Raspberry Pi Zero 1 W

The Raspberry Pi Zero only has 512Mb of memory, so I checked the memory and CPU usage with the top command. Below is a snapshot, but we can safely assume that there is more than enough memory for this use -ase.

$ top
top - 16:41:55 up 8 min,  2 users,  load average: 1.18, 1.65, 1.04
Tasks:  92 total,   1 running,  91 sleeping,   0 stopped,   0 zombie
%Cpu(s): 36.0 us, 41.6 sy,  0.0 ni, 22.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st 
MiB Mem :    427.9 total,    178.8 free,    168.1 used,    129.2 buff/cache     
MiB Swap:    512.0 total,    512.0 free,      0.0 used.    259.8 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                                                                                
  755 root      20   0  109708  13224  11336 S  72.4   3.0   4:45.71 libcamera-hello                                                                                                                                                        
  542 root      20   0  191808  42656  11792 S   1.0   9.7   0:27.00 java                                                                                                                                                                   
  790 pi        20   0    9612   4320   2364 R   1.0   1.0   0:00.25 top 

As you can see from the top output, with only 512Mb of memory, there is still enough left even when running both the camera and Java application.

Small Java application to control the camera running an a Raspberry Pi as an HDMI camera by executing the libcamera-hello application included in Raspberry Pi OS.

Conclusion

It would be nice if the settings could be changed on-the-fly, instead of restarting the camera. But as the cameras are connected to my network and easily updateable, and I find how to do this, or you can help me to achieve this, it will be very easy to replace the Java app…