How-tos

Hiding Telemetry Inside a Video Stream

· 3 min

The timestamp rides inside the video, not beside it.

A drone or robot camera rarely sends video alone. You also want to know when each frame was captured, where the vehicle was, what the sensors read. The usual approach is a second channel next to the video, and then a fight to line the two up at the other end.

The video-metadata repository from our lab at RBCCPS, IISc tries the other approach: put the data inside the video stream itself. Its README is empty, so this post walks through what the code (one main.cpp and one header, 2021, version 0.1) actually does.

The trick: SEI messages

An H.264 stream is a sequence of NAL units, each starting with the bytes 00 00 00 01. Most carry picture data, but type 6 is SEI, Supplemental Enhancement Information. Decoders that don't care about an SEI message simply skip it, so you can add one without breaking playback in any normal player.

One SEI flavour, user data unregistered (payload type 5), exists for exactly this: a 16-byte UUID followed by whatever bytes you like.

Step 1: Build the SEI unit

h264_sei_pack.h builds the NAL unit by hand:

  1. the start code 00 00 00 01;
  2. the NAL type byte 6;
  3. the payload type, then the payload size, each written in H.264's variable-length style (a 255 byte for every full 255, then the remainder);
  4. the payload bytes;
  5. the trailing byte 0x80.

The payload is a fixed UUID plus a text string. Here is the detail worth knowing: the code formats that string to look like the banner the x264 encoder writes itself, x264 - core 148 - ... options: %s, and puts the telemetry where the options normally go. There is also a second mode with a self-defined payload type (100) and the bare string.

Step 2: Capture and encode

main.cpp ties it together with OpenCV and FFmpeg:

Step 3: Stamp every packet

Each time the encoder hands back a packet, the code reads clock_gettime(CLOCK_REALTIME), turns it into microseconds since the epoch, packs that number into an SEI unit, and writes the SEI followed by the packet. So every compressed packet in the output is preceded by the wall-clock time it left the encoder.

timestamp = get_microseconds();
h264_sei_pack((uint8_t*)sei, &len, timestamp, true);
fwrite(sei, 1, len, stdout);
fwrite(pkt->data, 1, pkt->size, stdout);

On the receiving side you scan for NAL type 6, check the UUID, and read the number back. Compare it with your own clock and you have per-packet latency; replace the timestamp with GPS or IMU values and you have telemetry locked to the picture.

What it is, and what it isn't

This is a small, honest prototype, and it shows:

The idea is still a good one to copy. Because the metadata is part of the stream, it survives whatever carries the stream, a file, RTP, a recorder, without a second channel to keep in sync. The full source is on GitHub.

Share