Skip to content

Check the no-B-frame constraint in doctor and the encoder - #373

Merged
kstonekuan merged 3 commits into
Hebbian-Robotics:mainfrom
Sagar-024:feat/368-doctor-encoder-bframe-check
Sep 3, 2026
Merged

Check the no-B-frame constraint in doctor and the encoder#373
kstonekuan merged 3 commits into
Hebbian-Robotics:mainfrom
Sagar-024:feat/368-doctor-encoder-bframe-check

Conversation

@Sagar-024

@Sagar-024 Sagar-024 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #368.

  • Doctor now classifies picture coding types. _check_video_payload takes the count from scan_picture_coding_types(...).picture_count and reports a video-b-picture error finding when b_picture_count > 0, one NAL walk on the hot path, no double walk.
  • On an unparseable slice header the doctor delegates to count_h264_pictures on the error path and lets its own ValueError emit the finding, so video-invalid-slice-header stays byte-identical for both pinned kinds and the existing guard tests pass untouched.
  • Precedence: a malformed slice header wins over any B claim. The B finding is additive and never fires on an unclassifiable payload.
  • _enforce_encode_guarantees scans the joined stream and raises VideoEncodeError naming the first access unit that carries a B picture. The per-unit rescan runs only on the error path.
  • Mutation proof: deleting the doctor B check fails test_doctor_reports_a_b_picture_from_slice_headers, deleting the encoder B check fails both encoder tests with DID NOT RAISE VideoEncodeError.
  • Timing, interleaved A/B, median of warm iterations, conforming bframes=0 streams: 300 messages 2.64 ms before and 2.96 ms after (+12.1%), 3000 messages 24.67 ms before and 26.63 ms after (+7.9%). The delta is the same count-vs-scan gap you measured on refactor(video): serve both slice-header readers from one NAL walk #358, now buying the doctor the B classification.
  • docs/FORMAT.md closes both gap notes and adds the video-b-picture row. 1415 passed / 6 skipped, ruff, format, and ty clean.

Sagar-024 and others added 3 commits September 3, 2026 07:54
hflow doctor now reports video-b-picture when a canonical video
message's slice headers classify a picture as B. The count comes from
scan_picture_coding_types(...).picture_count so the hot path walks the
NALs once; on a scan refusal it delegates to count_h264_pictures, whose
caught ValueError keeps both pinned finding texts byte-identical and
reproduces the pre-classification behavior when first_mb_in_slice
parses and slice_type does not.

_enforce_encode_guarantees now refuses any B picture from the encoder,
naming the offending access unit via a per-unit rescan on the error
path. docs/FORMAT.md gains the video-b-picture row and closes both
open gap notes.

Refs Hebbian-Robotics#368
Prose only. The inserted sentences left lines between 30 and 96 columns
in a paragraph otherwise wrapped near 78.

@kstonekuan kstonekuan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, merging. The constraint I was most worried about is the one you handled best.

I diffed every message and every doctor finding for the same payloads, main against this branch. Exactly one line changes:

 all-zero payload                       -> ['video-invalid-slice-header']
 terminating bit, truncated suffix      -> ['video-invalid-slice-header']
 valid first_mb, truncated slice_type   -> ['video-not-aud-delimited']
 well-formed I slice (type 2)           -> ['video-not-aud-delimited']
-well-formed B slice (type 1)           -> ['video-not-aud-delimited']
+well-formed B slice (type 1)           -> ['video-b-picture', 'video-not-aud-delimited']
 empty payload                          -> ['video-invalid-slice-header']

Both count_h264_pictures messages are byte-identical, and so is ensure_access_unit_delimiter's behaviour through them. The third row is the subtle one: a header whose slice_type is truncated but whose first_mb_in_slice parses still counts and still reports no slice-header error, which is the per-field divergence #358 preserved. Delegating to count_h264_pictures on the error path rather than translating the scan's failure is what keeps that true, and it costs nothing on the hot path because it only runs when the scan already refused.

You also pinned that delegation, which I was about to check and did not have to. Removing it:

error-path delegation removed  -> 4 failed
  test_doctor_keeps_both_pinned_count_messages_when_the_scan_refuses
  test_doctor_reports_invalid_slice_header_over_b_picture_when_a_header_is_malformed
  test_nonconforming_ros2_video_is_reported
  test_nonconforming_video_is_reported

The first two are yours, written for exactly that property. The other two are pre-existing tests that would have caught it, which is the better kind of evidence.

Both new checks bite:

doctor B finding removed      -> test_doctor_reports_a_b_picture_from_slice_headers
encoder B guarantee removed   -> test_encode_guarantees_raise_on_a_b_picture_naming_the_unit,
                                 test_encode_guarantees_find_a_b_picture_in_a_later_unit

Scanning the joined stream first and only rescanning per unit on the error path is the right shape: the encoder pays one scan on success and the unit index costs nothing unless something is actually wrong.

On the timing, and this part is my fault rather than yours. Measured independently over real per-message access units from a conforming bframes=0 stream:

              main       branch
 300 messages  3.84 ms    4.25 ms   (+10.7%)
3000 messages 38.16 ms   41.97 ms   (+10.0%)
findings on conforming video: [] on both sides

Your numbers and mine agree on the magnitude. DoD 6 said "does not regress measurably", which was me writing the wrong sentence: classifying a second Exp-Golomb field per NAL cannot be free, and what I actually meant was the clause right after it about not walking the NALs twice. You did that, and reported the delta instead of burying it, which is the correct response to a badly worded requirement. In absolute terms a 900-message episode pays about a millisecond against a doctor run that reads the whole MCAP.

Pushed one fixup, dc638f7: prose reflow only. The inserted sentences left that paragraph with lines between 30 and 96 columns where the rest wraps near 78. Also worth noting you closed only the gap this PR closes and left the non-VCL-NAL-before-first-AUD note standing, which a careless edit would have taken with it.

Gate clean: ruff check, ruff format --check, ty check, lychee on FORMAT.md, 1428 passed / 6 skipped.

Closes #368.

@kstonekuan
kstonekuan merged commit 1f1db85 into Hebbian-Robotics:main Sep 3, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The no-B-frames constraint is refused on write but never checked by doctor or the encoder

2 participants