Summary & Background

The Panasonic Lumix GX80 is a mirrorless, interchangable lens micro four thirds camera. Some people have used it as a streaming webcam by connecting the camera's HDMI output to a HDMI-to-USB capture card (cost ~£120). It also has wifi connectivity, and can be controlled remotely - including a live preview over wifi.

I set out to see I could use that wifi live preview like a webcam.

In summary, I was able to get it to work - but with a lot of caveats. The live preview is 640x480 30fps and with some software trickery, I was able to use that video as a webcam in video calls.

Sample output

This image is captured raw from the mjpeg stream:

A single frame of camera output

FotoForensics estimates its compression level as 49%. The video stream has a bandwidth of about 6.8 Mbps and runs at 30 frames per second (even if your camera is PAL and records video at 25fps)

Sample video

Quick Start

  • Install dependencies something like this (assumes you have java and maven already, if not install them too):
sudo apt-get install ffmpeg v4l2loopback-utils
sudo modprobe v4l2loopback devices=1 video_nr=10 card_label="LoopbackCam" exclusive_caps=1
git clone
cd lumix-wifi-webcam
mvn package
sudo sysctl -w net.ipv4.ipfrag_time=5
# This undoes CVE-2018-5391 mitigation
sudo sysctl -w net.ipv4.ipfrag_high_thresh=4194304 
  • Turn on your camera, and connect it to wifi. I recommend using your router's WPS button so you don't have to type a password on a camera touchscreen.
  • Give your camera a fixed IP address in your router's DHCP settings, and note down what it is.
java -jar target/lumix-wifi-webcam.jar [your webcam's IP]

Credit where it's due

lenuisible for reporting the stream is a MJPEG video over UDP, and a Java stream viewer and Martin Pecka for keeping that alive and where I could find it. BTM_Pix and Vladimir Goncharov for additional info on Lumix remote control.

You mentioned a lot of caveats?

  • The resolution of 640x480 is nothing to write home about, and is quite heavily compressed.
  • I encountered a latency of about 130ms - 4 frames - meaning sound and video are out of sync by an amount people will notice.
  • The mjpeg-over-UDP protocol doesn't work very well in the presence of dropped packets or link congestion. This can lead to seemingly random performance degredation. Mitigations for consequences of this have security implications.
  • The GX80 can't be powered by USB while it's operating - so you need a 'dummy battery' power supply if you don't want your battery to run out (search for DMW-DCC11 if you want one)
  • In the course of my experiments, I managed to trigger several bugs in the camera's firmware - including one that froze the camera's entire interface, including the on/off switch, so I had to eject the battery to restart the camera. This doesn't seem like a solid foundation for a stable video system.
  • The GX80's wifi interface has no security, except the fact most users will never connect it.

Oddities of the mjpeg-over-udp protocol

Each frame is a single UDP datagram - making each datagram much larger than the ethernet MTU, and leading to packet fragmentation.

Wireshark screencapture

Here we see a 27 kilobyte image split into 19 fragments. If any of those 19 fragments are lost, the frame is lost. If you've got slow wifi, this can happen a lot.

Variable-length prefix

The mjpeg-over-UDP datagrams had a prefix before the JPEG data - usually of the same length, but occasionally varying. What's going on with that?

I reverse-engineered enough of the protocol to learn that:

  1. The length of the header is given in the header; there's a 16-bit integer starting at index 30; add 32 to that and you've got the index of the first byte of the JPEG data.

  2. The reason the header varies in length is it contains info about things that cause the camera to show things on the screen (like face detecton and points selected by autofocus) and the number of such things can vary.

IP fragment reassembly queue exhaustion

On Linux, the video stream would sometimes freeze for tens of seconds at a time, with UDP reads timing out as if no UDP traffic was being received - but Wireshark would indicate UDP packets were still arriving just fine.

It turns out the Linux kernel has a queue of partly-reassembled IP packets, waiting around for the missing fragments to come in. In the default configuration this queue has a fixed size of ~260 kilobytes in memory, doesn't discard stored contents for 30 seconds, and silently discards new packets when full.

With 27 kilobytes per frame, 30 frames per second and a 260 kilobyte limit on fragmented packets, if 10 frames drop a single fragment within 30 seconds the UDP socket would receive nothing until the oldest fragmented packet had aged out of the queue.

To work around this, I increased the fragment reassembly queue space - which undoes CVE-2018-5391 mitigation but I deem that tolerable as I'm not running a public-facing server. I also reduced the fragment reassembly time from 30 seconds to 5.


27 April 2020

Tags · Home · Archive · Tags