System designHow-to

Save the h264 camera stream to a file before your parser

Tap one capture with a tee: one branch writes the h264 stream to file while the other feeds your parser. Pipelines, container choice and storage math.

LumaSpec Vision10 min read Share on LinkedIn

On this page 10 sections
  1. 01One source, one tee, two consumers
  2. 02What you need
  3. 03Wire the tee into the pipeline
  4. 04Choose the container: raw h264, MP4 or TS
  5. 05Parameter sets decide whether the raw file plays
  6. 06Worked example: a 4 Mbit/s stream recorded for an 8-hour shift
  7. 07Where this pipeline runs
  8. 08Verify the recording and avoid the classic failures
  9. 09Which file to commit to, per case
  10. 10Questions

You need a copy of the camera's H.264 feed on disk — evidence for a customer complaint, a replay file for tuning your detection — while the live path through the parser and decoder keeps running at full rate. The answer is not a second capture and not a second pipeline. It is one source, one tee and one queue per branch: the tee hands every H.264 byte to both consumers, the record branch writes the file, and the decode branch feeds h264parse and the decoder exactly as before. This guide shows the pipeline changes for a deepstream-test4-style app, which container to pick, the storage math for a shift-long recording and the checks that prove the file plays before you rely on it.

One source, one tee, two consumers

A tee is a GStreamer element with one input and an output pad for every branch you request. Each branch receives the same buffers, so the recorded file and the decoded image cannot disagree — there is only one capture point. Give every branch its own queue immediately after the tee. The queue is a thread boundary with its own buffer: when the disk hesitates, the record branch waits there while the decode branch keeps pulling at full rate. Without that queue the tee blocks, and the whole pipeline — your live detection included — stalls.

Do not solve this with a second pipeline that opens the same source. The two opens compete for the device and the captures drift apart, so the file stops matching what the live path saw.

What you need

  • Your working decode pipeline. The sample you started from reads a file with filesrc, then runs h264parse, nvv4l2decoder and nvstreammux; your live version has the real source in place of filesrc.
  • The element that actually outputs H.264. If your feed is already an encoded stream, that element is not nvarguscamerasrc, which is the raw Bayer and ISP path — find the element whose caps advertise H.264 (media type video/x-h264) and put the tee on its output.
  • gst-launch-1.0 for a command-line trial before you touch application code.
  • Storage sized by the worked example below.
  • If your source is a machine-vision camera feeding raw frames instead of an encoded stream, record at the capture step instead: the capture-and-save workflow for a GigE Vision camera covers that case, the area scan cameras finder narrows models by interface and shutter, and the camera configurator pairs the sensor with a lens and interface.

Wire the tee into the pipeline

  1. Find the tee point. It sits between the element that outputs H.264 and the parser: between filesrc and h264parse in the sample, directly after your source element in a live feed.
  2. Name the tee so both branches can address it, replacing the source-to-parser link with tee name=t.
  3. Add the record branch — the raw elementary stream, exactly the bytes your parser would consume:
t. ! queue ! filesink location=rec.h264
  1. Reconnect the decode branch and leave everything after it untouched:
t. ! queue ! h264parse ! nvv4l2decoder ! nvstreammux ! (rest of your app)
  1. Or record a container instead of raw bytes — the next section helps you choose:
t. ! queue ! h264parse ! mp4mux ! filesink location=rec.mp4

Here h264parse does double duty: it timestamps the stream for the muxer and converts the byte stream into the format an MP4 file requires. 6. Trial the pipeline from the command line before touching your application: run it with gst-launch-1.0 and its send-EOS-on-shutdown flag, press Ctrl-C once when you want to stop, and wait for the shell prompt before killing anything.

Choose the container: raw h264, MP4 or TS

Recording Pipeline tail Opens in a player Survives a hard kill Best for
rec.h264 queue ! filesink Only with parameter sets in-band; no reliable seeking Yes Replays into your own parser
rec.mp4 queue ! h264parse ! mp4mux ! filesink Yes, seekable No — needs EOS Archives a customer can open
rec.ts queue ! h264parse ! mpegtsmux ! filesink Yes, even mid-write Yes Unattended cells that can lose power

The raw file is the smallest and the most faithful: filesink writes the elementary stream untouched, so you can replay it into your parser or remux it later without re-encoding. What it lacks is an index and timestamps, so players struggle to seek in it.

An MP4 writes its playback index at end-of-stream. Stop the pipeline cleanly and the file opens anywhere; kill the process first and it will not open at all, even though the video data is on disk. A transport stream has neither weakness: it stays playable while it grows and after an unclean stop, which makes it the default for unattended recording.

This pipeline records the compressed stream. If the evidence must show what your application rendered — overlays, measurements, reject marks — you are recording decoded frames instead, which is a different job: see VideoWriter writes an empty AVI file: feed it BGR frames for frame-by-frame video, or save an image with drawn overlays as a file any PC can open for still evidence.

Parameter sets decide whether the raw file plays

An H.264 decoder cannot start on slices alone; it needs the sequence and picture parameter sets (SPS and PPS) first. Encoders commonly emit them once at startup, or repeat them only with each keyframe. A recording that starts with the pipeline gets whatever the source sends; a capture cut from the middle of a running stream may contain no parameter sets at all, and players reject the file.

Fix it at the source when you can: the element that encodes usually has a property that repeats the parameter sets with every keyframe — list the element's properties with gst-inspect-1.0 and set it. Recording through h264parse into MP4 also solves it, because the parameter sets are stored inside the container. For a raw file that already exists without them, the SPS and PPS extraction walkthrough shows how to recover the parameter sets from a stream that carries them.

Worked example: a 4 Mbit/s stream recorded for an 8-hour shift

Inputs: a video bitrate of 4 Mbit/s and an 8-hour recording window. The bitrate is an encoder setting — 4 Mbit/s is a common working level for 1080p at 25 fps, so read the real value from your encoder configuration; the eight hours come from the retention policy, one shift here.

Rule in words: a constant-bitrate stream writes bitrate × time to disk, and bits become bytes by dividing by 8.

write rate (MB/s) = bitrate (Mbit/s) ÷ 8 GB per hour = write rate × 3600 ÷ 1000 GB per shift = GB per hour × hours

Arithmetic: 4 ÷ 8 = 0.5 MB/s. Per hour: 0.5 × 3600 = 1800 MB = 1.8 GB. Per shift: 1.8 × 8 = 14.4 GB.

Result: one recorded stream costs about 14 GB per shift, written at 0.5 MB/s. That write rate is far below any SSD's sustained speed, so recording one stream does not throttle the pipeline — capacity is what you manage.

Decision: size storage by retention period. Two weeks of shifts is 14 × 14.4 ≈ 202 GB, which fits a 256 GB drive alongside the operating system and your application; thirty days is 30 × 14.4 = 432 GB; a 1 TB drive holds 1000 ÷ 14.4 ≈ 69 shifts. The figures scale linearly: at 8 Mbit/s everything doubles to 3.6 GB per hour, and six cameras at 4 Mbit/s write a combined 3 MB/s and about 86 GB per shift — still easy for an SSD, but only if deletion keeps pace. Add roughly 10% for container overhead and rotate recordings on a schedule instead of when the disk fills. Bitrate itself is a budget: encoding two H.264 streams on one embedded VPU works the same arithmetic from the encode side.

Where this pipeline runs

The tee, the parser, the muxer and your application all live on the capture box, so the box needs a port per source, memory for the decode path and the storage the example above sizes. A vision controller puts machine-vision ports and recording storage in one unit.

The MV-VC2140-128G40-1T pairs a 128 GB SSD with a 1 TB HDD and 8 GB of RAM. On the example's numbers, that hard drive holds about 69 shifts of a single 4 Mbit/s stream before rotation.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

For several encoded feeds on one box, the MV-VC3301H-128G66 offers 5+ LAN ports and 3-4 USB 3 ports around an Intel Core i3; the MV-VC5525-256G70-NN adds PoE camera ports, 8 built-in channels, 32 GB of memory and a 256 GB SSD.

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
$2,419.00View part
Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
$4,479.00 · quotedView and quote

Decode load, not disk, sets the ceiling once recording costs so little bandwidth: every stream you decode spends processor or accelerator time, and an on-screen preview multiplies the cost. Where headroom runs short, the low-resolution preview stream that cuts decoder CPU load buys some of it back.

Verify the recording and avoid the classic failures

Verify, in this order:

  1. Time it. Compare the frames per second your application processes before and after adding the tee; they should match within a fraction of a percent.
  2. Watch the file grow. Raw .h264 and .ts grow from the first second; an MP4 is only complete after a clean stop.
  3. Play it mid-shift. Open the .ts or raw file in a player while recording continues; test an MP4 only after end-of-stream.
  4. Replay it through your decoder. Point filesrc at the finished file, run it through h264parse and the decoder, and count frames: an 8-hour file at 25 fps must yield 8 × 3600 × 25 = 720,000 frames.

Then the failures, most likely first:

  • A missing queue on a branch. The tee blocks, the pipeline stalls, and the symptom surfaces as buffer timeouts in the live path — camera buffer timeout on external trigger is the same failure seen from the trigger side.
  • A second pipeline on one source, whose captures drift from the live image.
  • A force-killed MP4 with no index — use end-of-stream or TS.
  • A mid-stream start on an encoder that emits parameter sets only once — the raw file will not play; fix it as the section above describes.
  • A disk allowed to fill — rotate recordings on schedule, because a full drive stalls every branch at once.

Which file to commit to, per case

  • Replay into your own parser at the smallest overhead: raw .h264, with the encoder set to repeat parameter sets per keyframe.
  • An archive a customer opens on any PC: MP4, always stopped with end-of-stream.
  • An unattended cell that can lose power: MPEG-TS.
  • Evidence of what the application rendered: record decoded frames with overlays after the decoder instead.

If your source turns out to be a machine-vision camera feeding raw frames, record at the capture step rather than around a parser that does not exist — the camera finder, the systems topic page and the contact page take you the rest of the way. Send your stream count, bitrate and retention period, and we will size the box with you.

Questions

Will writing the file while decoding drop frames?

No, provided every branch after the tee starts with its own queue. The queue gives the record branch its own buffer, so a slow disk waits there while the decode branch continues at full rate. Without a queue the tee blocks both branches, and the first symptom is a stalled live path or timeouts elsewhere, not a bad file.

Why does my recorded MP4 fail to open after a power cut?

An MP4 writes its playback index at the very end, when the pipeline sends end-of-stream. If the process is killed first, the video data is on disk but the index is missing, so players reject the file. Record MPEG-TS in cells that can lose power: a transport stream stays playable up to the last written moment.

What bitrate should I record inspection footage at?

Treat it as an encoder setting you tune, not a constant. As a starting point, 4 Mbit/s at 1080p and 25 fps is a common working level; raise it in steps until the smallest feature you must re-read — a serial number, a barcode — is sharp when you replay the file, and re-run the storage math each time because file size scales linearly with bitrate.

Can I turn a raw .h264 capture into an MP4 later?

Yes, without re-encoding. Read the file back with filesrc, pass it through h264parse into an MP4 muxer and write a new file; the video data is copied untouched, so it takes seconds even for large captures. The raw capture must contain the parameter sets — if it does not, the remux produces a file that still will not play.

Do I need to record the decoded image as well?

Only if the evidence must show what your application rendered — bounding boxes, measurements, reject marks. The raw stream replays into your parser and decodes to the identical image, which is usually stronger evidence. If you do need the rendered picture, grab frames after the decoder and write them with overlays; that is a separate write path from this one.