System designTroubleshooting

Frame didn't arrive within timeout: one wait call per frame

Why 'frame didn't arrive within timeout' fires seconds after stream start, how a second blocking wait_for_frames call starves the queue, and the fix.

LumaSpec Vision10 min read Share on LinkedIn

On this page 8 sections
  1. 01Why does the error land about six seconds after start?
  2. 02What does the timeout number actually measure?
  3. 03How do you find the duplicate wait call?
  4. 04What does a correct acquisition loop look like?
  5. 05Which other coding mistakes raise the same error?
  6. 06When is the timeout telling the truth about hardware?
  7. 07How do you size the wait and prove the fix?
    1. A 30 fps camera, start to proof
    2. Before you close the ticket
  8. 08Questions

The error reads the same on every bench: "frame didn't arrive within timeout", thrown a few seconds after your acquisition program starts, on every run. Instinct points at the camera, the cable or the network, so the afternoon goes to swapping hardware. In most of these cases the cause is one line of code: a second blocking wait for frames in the same loop. The first wait consumes the frame, the second waits for a frame that will never be handed to it, and the call expires at its full ceiling. This guide walks the diagnosis in order of probability: read the timing signature, find the duplicate wait, rewrite the loop so each frame is pulled exactly once, then confirm that a timeout only fires when the stream really stops.

Why does the error land about six seconds after start?

Because your loop blocks twice for a frame that is delivered once: the second blocking wait finds the queue empty and expires one full timeout later, which puts the error at start-up delay plus 5000 ms, roughly six seconds after launch on the default ceiling.

Break the six seconds down. Enumeration and exposure search take 0.5-1.0 s before the first frame, the first wait returns it, and the second wait then blocks with nothing coming. Add one timeout to the start-up delay and the error lands between five-and-a-half and six seconds, every run. Other causes do not land there. Memory exhaustion from retained frames appears tens of seconds to minutes in and throws an allocation error, not a wait timeout. A failing link drops frames intermittently and usually tracks cable motion or machine vibration.

What you observe Most likely cause 60-second test Fix
Error at start-up plus one timeout, every run Duplicate blocking wait in the loop Comment out the second wait call One wait per frame, share the object
Frame rate halved, latency climbing Two waits both returning from one stream Log two returns per frame number One consumer pulls, others copy
Error under vibration or cable flex Loose connector, non-drag-chain cable Move the cable while logging Screw-lock, drag-chain rated cable
Random cameras drop at power-up PoE budget exceeded on a shared switch Sum camera watts against the switch budget One camera per PoE port
Only the first frame of a run times out Wait ceiling below start-up search Log the first frame's arrival time 2000 ms first-frame allowance

If your signature is not in the first row, diagnosing a camera frame acquisition timeout widens the field to link, trigger and SDK causes.

What does the timeout number actually measure?

It is the longest one blocking call will wait for one frame, counted from the moment you call it — and the default ceiling covers exposure search and stream matching at start-up, not packet jitter on a healthy link.

On a healthy stream the ceiling is enormous next to delivery time. A 30 fps camera produces a frame every 1000 / 30 ≈ 33 ms, so a working wait returns within one frame period plus a few milliseconds. A call that reaches its ceiling spent about 150 frame periods receiving nothing. Congestion slows delivery; it does not silence it. Zero frames at one call, while frames flow to the rest of your program, is structural starvation: one consumer was handed every frame, the other none.

frame_period_ms = 1000 / fps silenced_periods = timeout_ms / frame_period_ms

At 30 fps with the default ceiling, silenced_periods = 5000 / 33 ≈ 150. A number that large means the call is not waiting for the next frame; it is waiting for a frame promised to someone else.

How do you find the duplicate wait call?

Search the acquisition path for every blocking call that returns a frame — wait_for_frames, grab, WaitForFrame — and count how many execute per loop iteration; more than one is the bug, and one of them is your timeout.

Duplicates hide in three places. A preview refresh that pulls its own frame inside the processing loop. A helper with an innocent name, get_newest_frame(), that blocks internally. And a second thread, display alongside processing, waiting on the same pipeline: the same fault one level up, two consumers against one delivery.

The bench test is a log line, not a debugger. Record the loop counter, the frame number and a millisecond timestamp at every return. Two returns sharing one frame number convict two consumers. Then comment out the second call and rerun; if the error vanishes, the fix is deletion.

What does a correct acquisition loop look like?

One blocking wait per iteration, one frame object, and every consumer — display, processing, saving — reads that same object; if a second part of the program needs frames, hand it a copy instead of waiting twice.

  1. Start and configure the stream once, outside the loop.
  2. Call the blocking wait once per iteration and store the frame set it returns.
  3. Extract every image you need — depth, color, infrared — from that one set.
  4. Pass references or copies to display, processing and saving; none of those functions calls a wait.
  5. Poll without blocking in user-interface code, sleeping a few milliseconds between polls.
  6. Keep the timestamp log running through the first ten minutes at production rate.
while running:
    frames = stream.wait(steady_wait)   # the only blocking wait
    publish(frames)                     # display, processing, saving read this copy

A preview that stops updating during capture belongs to the same family; see camera preview freezes after the first frame: channel checks when the window locks up. The continuous acquisition mode pattern for live image loops is built around this exact shape: one wait feeding every consumer.

Which other coding mistakes raise the same error?

Processing that runs longer than a frame period inside the arrival callback, a sleep in the acquisition loop, and two threads waiting on one pipeline — in that order — are the next three causes to eliminate.

Callback overruns. Running 120 ms of work inside the callback on a 30 fps stream consumes about 8 fps; the surplus of roughly 22 frames per second fills a 16-frame driver queue in 16 / 22 ≈ 0.7 s. Some stacks drop the oldest frame and limp on; others stall delivery, and the stall surfaces as your timeout. Move the work to a worker queue and return from the callback inside one frame period. Preview dropped frames: stalls and resource limits tracks the same failure from the UI side.

Sleeps in the frame loop. A 100 ms sleep consumes 10 fps against a 30 fps stream; 20 frames per second of surplus fill that queue in 0.8 s. Sleeps belong in the UI poll, never between frames.

Playback of recorded clips. A recording delivers frames at capture rate, and every frame you retain sits in memory, so the duplicate-wait fault bites hardest there. Troubleshooting wait_for_frames frame drops in recorded bag files works that path end to end.

A clean loop that still times out. When the log shows one return per frame and the error survives a rebuild, suspect an SDK build disagreeing with the runtime: camera SDK build mismatch: diagnose frame timeouts walks the version checks.

When is the timeout telling the truth about hardware?

When the error is intermittent, tracks cable flex or machine motion, or survives a loop with one wait per frame — then work the link: locking connectors, a dedicated PoE port, matched packet size and a drag-chain-rated cable, in that order.

On a GigE Vision link, set the camera's packet size and the NIC's MTU to the same value — 9000 bytes is the usual figure on a dedicated link — and read the resend counters before blaming the camera. Power is the quieter trap: IEEE 802.3af delivers 15.4 W at the port and 12.95 W at the camera, so four cameras sharing one small switch pass an empty bench and brown out on the floor. Give each camera its own powered port. The MV-GE2002P is a dual-port PoE NIC, so each camera gets a port and a power budget of its own.

Frame grabbersHikrobot MV-GE2002P Dual-port PoE NIC, full height V2.0MV-GE2002P
Interface
GigE
Ports
2 ports
$249.00View partBuild a system with it

A NIC that discards frames leaves evidence in its own counters first; how NICs detect and handle invalid Ethernet frames explains what to read there.

Connectors are the most common intermittent on a moving axis: an unlocked latch works loose under vibration. The LS-CBL-0199 is a 3 m GigE camera cable with screw locks, rated for drag chains.

Cables3 m GigE camera cable, high-flex (drag chain), screw locks (LS-CBL-0199)LS-CBL-0199$54.00View partBuild a system with it

USB3 cameras stall for different reasons: shared hub bandwidth and unlocked connectors. Put the camera on a direct port, never a bus-powered hub; fixing USB bandwidth stalls on depth cameras covers the rest. The LS-CBL-0219 is a 3 m USB3.0 camera cable with the same drag-chain rating.

Cables3 m USB3.0 camera cable, high-flex (drag chain) (LS-CBL-0219)LS-CBL-0219$62.00View partBuild a system with it

When the log points at the link, browse machine vision cables and frame grabbers; when the failure follows one camera body everywhere, the area scan cameras finder and the camera step of the system configurator are where to re-spec.

How do you size the wait and prove the fix?

Keep the first-frame allowance at 2000 ms, set the steady-state wait to ten frame periods — 330 ms at 30 fps — and prove the fix by cutting the stream: after the repair, the error must fire inside that short window and never during normal running.

first_frame_allowance = 2000 ms # enumeration + exposure search ≈ 0.5-1.0 s, doubled steady_wait = 10 × (1000 / fps) # ten frame periods

A 30 fps camera, start to proof

Start-up takes 0.8 s on this rig: enumeration and exposure search, then frame 1 returns to the first wait. The second wait starts at t = 0.8 s, receives nothing, and expires at 0.8 + 5.0 = 5.8 s — the six-second signature, no hardware involved. Delete the second wait and the loop settles to one 33 ms period per frame. The steady wait becomes 10 × 33 = 330 ms. Cut the stream by disabling the camera's port in software, and the error surfaces within 330 + 33 ≈ 0.4 s. That is the proof: a timeout now means the stream stopped. Ten minutes of clean logging with flat latency closes the ticket. One bench rule while testing recovery: never look into an IR projector or illuminator at close range.

Before you close the ticket

  • Exactly one blocking wait per loop iteration, counted in the log.
  • No two returns share a frame number.
  • Loop period equals the frame period (33 ms at 30 fps), latency flat over ten minutes.
  • First frame arrives within 2000 ms of start, on every start.
  • A cut stream surfaces an error inside half a second.
  • Cable locked, one camera per PoE port, packet size matched to the NIC's MTU.

If the loop is clean and the link checks pass but frames still stall, diagnosing a camera frame acquisition timeout takes the deeper path; the vision guides hub indexes the neighbouring failures, and the contact page puts an applications engineer behind your log lines.

Questions

Can a short recording cause the frame timeout?

No, and the arithmetic rules it out. An 848 × 480 depth frame at 2 bytes per pixel is about 0.8 MB, so 30 fps costs roughly 24 MB/s of memory. Nineteen seconds is about 460 MB, and even a 4 GB machine holds around 160 seconds before keep()-style retention exhausts it — and a memory failure throws an allocation error, not a wait timeout. An error six seconds in, from a wait that never returned, is the duplicate blocking wait.

Should you raise the timeout to get past the error?

No. A longer wait does not deliver one extra frame; it makes each failure slower and hides the starvation from your logs. The only wait worth extending is the first frame after start, where enumeration and exposure search legitimately take up to a second. Once the stream runs, a wait that needs more than a few frame periods is telling you frames are not arriving — a defect to fix, never a number to raise.

Does the one-wait rule apply to GigE and USB3 industrial cameras too?

Yes. Under GigE Vision, USB3 Vision and GenICam, a grab or wait call consumes a buffered frame once — it is a queue pop, not a broadcast. Two consumers that both grab from one handle starve each other exactly as two wait calls in one loop do. Run one acquisition thread that pulls frames and publishes copies; that pattern holds across interfaces, SDKs and recorded playback.

Why does the second wait block for the full timeout instead of returning the next frame?

Because a complete frame set is assembled once per frame period and handed to exactly one waiting call. Two blocking waits per iteration demand two sets every 33 ms while the camera produces one, so at start-up — while stream matching is still settling — one call receives nothing at all. After about 150 silent frame periods the 5000 ms ceiling expires and the error throws.

Do hardware-triggered cameras hit the same fault?

Yes, and the arithmetic is harsher. On a triggered camera a frame exists only after a trigger pulse, so a second blocking wait waits for the next pulse — at one pulse per second it doubles cycle time, and at any interval longer than the ceiling it times out. Pull one frame per trigger event and poll the trigger-ready status instead of waiting twice.