Skip to content

docs(vdo): key-frame SPS/PPS live in the stripped header; add header_bytes() - #250

Open
fishloa wants to merge 1 commit into
AxisCommunications:mainfrom
fishloa:docs/vdo-keyframe-header
Open

fishloa wants to merge 1 commit into
AxisCommunications:mainfrom
fishloa:docs/vdo-keyframe-header

Conversation

@fishloa

@fishloa fishloa commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #247.

data_copy() strips the [0, header_size()) header, and on H.264/H.265 key frames that header is exactly where VDO carries the SPS/PPS (and VPS) parameter sets — so data_copy() on a key frame returns only the coded slice, and a caller scanning those bytes for parameter sets never finds them even on a perfectly healthy stream.

Observed on an ARTPEC-6 / firmware-11 H.264 IDR buffer:

header_size        = Some(41)
as_slice()[..size] = 00 00 00 01 67 64 00 29 ...   // 0x67 = SPS, then PPS, then the IDR slice
data_copy()        = 00 00 00 01 65 ...            // 0x65 = IDR slice only — SPS/PPS gone

This PR:

  • documents the behaviour on data_copy() and header_size(), and
  • adds StreamBuffer::header_bytes() returning the [0, header_size()) slice, so the "grab the codec config once" path is obvious and hard to get wrong.

No behaviour change to existing methods; header_bytes() mirrors data_copy()'s existing unsafe access pattern.

…r; add header_bytes()

data_copy() excludes the [0, header_size()) header, and on H.264/H.265 key
frames that header is where VDO carries the SPS/PPS (and VPS) parameter sets —
so a caller scanning data_copy() bytes for parameter sets never finds them even
on a healthy stream. Document this on data_copy()/header_size(), and add
StreamBuffer::header_bytes() returning that header slice so the 'grab the codec
config once' path is obvious and hard to get wrong. Closes AxisCommunications#247.
@fishloa
fishloa requested a review from a team as a code owner July 18, 2026 16:35

@apljungquist apljungquist left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

Comment thread crates/vdo/src/lib.rs
/// The full access unit is `header_bytes()` followed by `data_copy()`
/// (equivalently, [`as_slice()`](StreamBuffer::as_slice) up to
/// [`size()`](StreamBuffer::size)).
pub fn header_bytes(&self) -> std::result::Result<&[u8], Error> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I haven't looked at what interpreting these bytes looks like, but I am wondering if perhaps there is an opportunity here to encode some more information about the header?
i.e. what would it look like if we returned something like Result<Option<Header>, Error>

Comment thread crates/vdo/src/lib.rs
/// [`header_bytes()`](StreamBuffer::header_bytes), or use
/// [`as_slice()`](StreamBuffer::as_slice) for the full `[header][data]`
/// buffer.
pub fn data_copy(&self) -> std::result::Result<Vec<u8>, Error> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe we should replace data_copy with data_bytes for consistency and flexibility in a follow up commit?

Adding .to_vec() to call sites is a minor inconvenience.

Comment thread crates/vdo/src/lib.rs
Ok(slice.to_vec())
}

/// Returns the frame's header bytes — the `[0, header_size())` prefix that

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[0, header_size())

Personally I would leave out details about where in the buffer the header bytes are from the commit message

@apljungquist apljungquist left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I like the context in the issue btw; I didn't read the issue at first because it looked like a wall of AI text but now that I have read it that section helps convince me that there is a real developer with a real use case behind the isse+PR 😄

Cost a few debugging cycles on real hardware (the stream looked fine, key frames arrived, but SPS/PPS never appeared) before header_size made it obvious. Filing so the next person doesn't repeat it. Happy to PR the doc note + accessor.

Comment thread crates/vdo/src/lib.rs
/// [`header_bytes()`](StreamBuffer::header_bytes), or use
/// [`as_slice()`](StreamBuffer::as_slice) for the full `[header][data]`
/// buffer.
pub fn data_copy(&self) -> std::result::Result<Vec<u8>, Error> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You expressed surprise that data does not include the header and I'm thinking maybe we can help future users by choosing better names for the concept?

Having two functions with similar names e.g. header_bytes and data_bytes would also help I imagine.

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.

data_copy() silently strips the key-frame header carrying SPS/PPS — document / add accessor

3 participants