Capturing webcam video on the BT Home Hub 5a
By Patrick Wigmore, , published: , updated
With my infrared webcam connected to my OpenWRT Home Hub, I initially planned to stream live video to another computer, where I would perform software motion detection.
Streaming was possible, but proved inadequate for the task. Eventually I settled on recording to local storage, without software motion detection.
Getting the webcam working
The first steps, in any case, were to install the webcam drivers.
The webcam, like most vaguely modern ones, implements the standard USB Video Class (UVC) interface and doesn’t need a special driver. Nevertheless, OpenWRT does not include the necessary kernel modules by default, which seems sensible for an operating system primarily intended for routers.
To install them, you can install the kmod-video-core
and kmod-video-uvc
packages using the opkg
package manager. I also installed kmod-usb-audio
, since the camera contains a microphone, but this ended up being redundant, because I did not end up recording audio.
The utility v4l2-ctl
can tell you which video formats a UVC camera supports.
$ v4l2-ctl -d /dev/video1 --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
Index : 0
Type : Video Capture
Pixel Format: 'YUYV'
Name : YUYV 4:2:2
Size: Discrete 640x480
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 160x120
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 176x144
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 320x240
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 352x288
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 1280x720
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 1920x1080
Interval: Discrete 0.200s (5.000 fps)
Index : 1
Type : Video Capture
Pixel Format: 'MJPG' (compressed)
Name : Motion-JPEG
Size: Discrete 640x480
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 160x120
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 176x144
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 320x240
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 352x288
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 1280x720
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
Size: Discrete 1920x1080
Interval: Discrete 0.033s (30.000 fps)
Interval: Discrete 0.050s (20.000 fps)
Interval: Discrete 0.067s (15.000 fps)
Interval: Discrete 0.100s (10.000 fps)
Interval: Discrete 0.133s (7.500 fps)
There are quite a lot of formats there, but the common 25fps frame rate is missing, and you have to use MJPEG if you want HD resolution at a reasonable framerate. It also only supports widescreen resolutions in HD sizes; all the smaller resolutions are 4:3-ish.
Similar but less extensive information can also be obtained from ffmpeg:
$ ffmpeg -f v4l2 -list_formats all -i /dev/video0
video4linux2,v4l2 @ 0x76b9f780] Raw : yuyv422 : YUYV 4:2:2 : 640x480 160x120 176x144 320x240 352x288 1280x720 1920x1080
[video4linux2,v4l2 @ 0x76b9f780] Compressed: mjpeg : Motion-JPEG : 640x480 160x120 176x144 320x240 352x288 1280x720 1920x1080
/dev/video0: Immediate exit requested
Getting MJPEG straight out of the camera is potentially quite useful, because it’s a compressed format that can theoretically be streamed over the network without re-encoding.
Streaming MJPEG
The problem with directly streaming the camera’s MJPEG is the bitrate. On this camera, it peaks at nearly 30Mbps for 1280x720 video, and reducing the resolution and framerate makes very little difference.
It seems the camera likes to maximise the quality of the stream, up to a designed bandwidth limit, rather than minimising the bandwidth required. This behaviour makes perfect sense for typical webcam use cases; it makes good use of the available USB bandwidth and usually the MJPEG would be transcoded into something else if it ever left the local machine. But it’s absolutely rubbish if you are planning to send the camera’s MJPEG stream over a network without reencoding.
I identified a couple of programs that can network-stream the camera’s MJPEG: mjpg_streamer
and ffmpeg
.
In practice, ffmpeg
is a bit heavy to run on the router, but it works fine on a more powerful machine.
Streaming the raw MJPEG video straight from the camera over HTTP over WiFi, the stream is watchable, but pretty choppy and not really suitable for taking archival recordings or performing motion detection.
The maximum reliable framerate I found I could use was 3fps. This does actually seem adequate. The main issue with choppy video is the inconsistent framerate, not the low framerate.
Using 5GHz WiFi enables higher framerates, sometimes as high as 30fps, but not consistently. In the end, 3fps is the maximum reliable rate.
On another machine, the stream sent from the Hedgehog Home Hub can be recorded quite effectively using, e.g.
ffmpeg -vcodec mjpeg -r 6 -i http://hedgehog-hh5a.lan:8080/?action=stream vcodec h264 ~/tmp/hhs.ts
But the unreliable frame rate was an issue, so I moved on to recording the video locally on the Home Hub.
Recording onto a USB flash drive
Preliminary testing with an old USB hub showed that it had the bandwidth to run the webcam in its highest bitrate video mode while a large file was read from a USB flash drive connected to the same hub. This showed there was enough USB bandwidth to record footage directly onto a USB memory stick.
I hoped to use the program motion
, to save footage only when motion was detected.
OpenWRT packages motion
, but it is not compiled with support for ffmpeg, which is necessary if you don’t want motion
to re-encode the video. (The movie_passthrough option to save the raw stream requires netcam_url, which requires ffmpeg.) I tried compiling a version of motion that does have support for ffmpeg.
But, in reality, the Home Hub doesn’t have enough memory or CPU power to run motion
, regardless of what options are enabled. So, I gave up on that idea. I would have to find another way to trigger recordings.
Installing kernel modules for USB Mass Storage
In order to use a USB flash drive on OpenWRT, first you have to install the USB Mass Storage kernel module and the VFAT filesystem kernel module. (Assuming the flash drive has a FAT filesystem.)
opkg install kmod-usb-storage kmod-usb-storage-uas kmod-fs-vfat
The UAS (USB Attached SCSI) support is unlikely to be necessary for a USB flash drive, but can improve performance for some hard drives and SSDs. I figured I should install it to avoid the possibility of forgetting to do so later.
I noticed some flakiness when using a USB flash drive with the Home Hub 5A. It seemed to get unmounted after some time, when I wasn’t paying attention. The block device seemed to get re-enumerated as /dev/sdb1, having previously been /dev/sda1.
dmesg revealed a host of blk_partition_remap: fail for partition 1
and FAT-fs (sda1): Directory bread(block 25603106) failed
errors at around the time when I was trying to access the USB drive.
Recording video into a file using v4l2-ctl
v4l2-ctl
seemed to be the most reliable method of recording from the webcam.
Unfortunately, when recording to a USB flash drive, some aspect of the process was quickly bogged down, causing a low framerate and pretty soon causing the flash drive to crash offline, leaving a dangling mountpoint.
Recording to /tmp was much more successful. OpenWRT’s /tmp is a tmpfs; a RAM disk; so the recording is held in main memory. The Home Hub 5a has much more RAM than it does internal flash storage, but unfortunately there is still only enough memory to store about 38MB of video in this manner. I was able to record nine seconds of footage: 700 frames; but not reliably. Sometimes even nine seconds was too much. Memory usage fluctuates, so a conservative time limit on the footage is necessary. This command records 270 frames at 30fps:
v4l2-ctl -d /dev/video0 -p 30 -v pixelformat=1,width=1280,height=720 --stream-count 270 --stream-poll --stream-user --stream-to-hdr /tmp/hhcapture
It is also possible to use --stream-mmap
instead of --stream-user
, but --stream-user
seems slightly more stable. I found that specifying --stream-user 1
, to request only 1 buffer instead of the default 3 allowed a larger file to be captured; 51MB.
After capturing, the video can be transferred to a more powerful machine and then put into a playable container format using ffmpeg:
scp root@192.168.2.222:/tmp/hhcapture ~/tmp/test2020-07.mjpg
ffmpeg -i ~/tmp/test2020-07.mjpg -framerate 30 -vcodec copy ~/tmp/test2020-07.mkv
Later on, once I was producing more videos, I converted the recorded files into something a bit smaller using:
ffmpeg -framerate 7.5 -i 2020-11-10T160919.mjpg 2020-11-10T160919.mp4
Or, less manually for multiple files:
for f in ./*.mjpg; do ffmpeg -n -framerate 7.5 -i $f $(basename $f .mjpg).mp4; done
In this case, the -n option has the effect of causing ffmpeg to convert only those files that have not already been converted, although it will also skip any where conversion was previously interrupted.
I also found that, if I sent a video to someone with an iPhone 5s, it had to be converted to the yuv420p colourspace:
ffmpeg -framerate 7.5 -i <input_file> -vf format=yuv420p <output_file>
Frame rate shenanigans
Unfortunately, v4l2-ctl
does not provide a way to specify the recording frame rate; you can only specify the frame rate that will be requested from the camera.
That might sound like it’s equivalent, but it turns out that the camera can, and will override the requested frame rate with a variable frame rate that adjusts to the lighting conditions. If there is plenty of light, you get the requested frame rate, but if the lighting is poor; like, oh, I don’t know, lets say you’re recording in pitch darkness with only a small pool of infrared light for illumination; then the camera reduces its frame rate so it can do a longer exposure for each frame. My first tests were in bright daylight, so it took me a while to figure out what was going on.
The problem with a variable frame rate is that v4l2-ctl
doesn’t store the erratic timing for the display of each frame. They’re just displayed at the fixed frame rate it was expecting. The video ends up looking like an old-fashioned hand-cranked film with everything speeding up and slowing down, to comic effect.
One way to work around the variable frame rate would be to use mjpg_streamer
to output timestamped individual JPEG files, and then process them using ffmpeg into a suitable video, taking the frametimes from the timestamps.
What I actually did, though, was fix the framerate at 7.5fps; it never goes below that (well, it is often actually 7.49, but close enough), so it’s guaranteed to be a fixed rate when 7.5 is specified. Realistically, the camera isn’t going to exceed 7.5fps for night vision anyway, and a 7.5fps video takes up much less storage space than one with more frames per second.
Why is the USB flash drive so flaky?
Sadly, even 7.5fps initially seemed to be too much for recording directly to USB, though it did seem less likely to crash the USB flash drive.
I eventually figured out that there was a power supply issue affecting the USB flash drive. Fixing the power supply issue enabled the USB flash drive to function correctly.