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:
- Using a Raspberry Pi as HDMI camera (December 12, 2021)
- Using the Raspberry Pi Autofocus Camera Module 3 as HDMI camera (April 4, 2023)
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:
- Raspberry Pi Zero 1 W as they are the cheapest and because the Zero 2 was not available at the time I was building these cameras.
- SD Card with Raspberry Pi OS 32-bit (no desktop).
- Raspberry Pi Autofocus Modules.
- 3D printed housing with modified front cover. Unfortunately, the license of the original model doesn’t allow me that file.
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:
--viewfinder-width 1920
: HD width--viewfinder-height 1080
: HD height--roi 0.0,0.0,1.0,1.0
: Crops the image extracted from the full field of the sensor. Accepts four decimal values, ranged 0 to 1, in the following format:<x>,<y>,<w>,h>
. Each of these values represents a percentage of the available width and heights as a decimal between 0 and 1. These values define the following proportions:<x>
: X coordinates to skip before extracting an image<y>
: Y coordinates to skip before extracting an image<w>
: image width to extract<h>
: image height to extract
--awb indoor
: Adjusts the color temperature. For my lightning,indoor
turns out to give the best results. Check the documentation for the other options.-f
: Show the camera full screen.-t 0
: Disables the timeout to keep the camera open.
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…