Skip to content

ffmpeg: receive SVAC 3.0 over RTP and RTSP - #14

Merged
widgetii merged 2 commits into
masterfrom
svac3-rtp
Oct 29, 2026
Merged

widgetii merged 2 commits into
masterfrom
svac3-rtp

Conversation

@widgetii

Copy link
Copy Markdown
Member

Why

FFmpeg can decode an SVAC 3.0 file with 0001, but not a camera's live stream. The SVAC 3.0 draft defines a byte stream and no RTP mapping. OpenIPC cameras stream SVAC 3.0 over RTSP in a mapping modelled on RFC 7798, and this reads it, so ffplay rtsp://camera/stream=0 plays them.

What

ffmpeg/0002-avformat-rtpdec-add-an-RTP-depacketizer-for-SVAC-3.0.patch adds libavformat/rtpdec_svac3.c, registered for SVAC3/90000:

  • Single NAL unit packet: the NAL unit, header included.
  • Fragmentation unit:
    • The NAL header, with type replaced by 28 (reserved in the draft's table 55). Security idcs, layer_id and temporal_id are kept.
    • Then an S|E|R|FuType(5) header, then a fragment of the bytes after the two-byte header.
    • S and E together is refused.
    • Reassembly uses ff_h264_handle_frag_packet with the rebuilt header.
  • Access units: the marker bit ends one.
  • SDP: sprop-sps / sprop-pps in fmtp become Annex-B extradata, which the 0001 wrapper already feeds to the decoder.

CI

  • Applies ffmpeg/0*.patch in order, each checked against the tree the previous ones made.
  • Builds FFmpeg with the sdp, rtsp and rtp demuxers and the udp, rtp and tcp protocols.
  • ffmpeg/rtp_check.sh sends every corpus stream as RTP over local UDP with ffmpeg/rtp_send.py, an independent packetiser of the same format. FFmpeg receives it through an SDP, and the result is held to the same golden framemd5 as the raw demuxer.
  • It runs at 1200-byte packets and at 300, where every patch is fragmented.

ffmpeg/README.md documents the mapping.

Verified

  • Locally, FFmpeg n9.0.2 with both patches:
    • all 8 corpus streams bit-exact against the golden hashes through RTP, at both packet sizes;
    • includes the multi-patch and library-picture streams.
  • Against a live Hi3516CV608 camera over RTSP/TCP: ffmpeg -rtsp_transport tcp -i rtsp://…/stream=0 -f framemd5 decodes 1080p with no warnings, one packet per picture.
  • Patch application: both patches apply in order to a clean n9.0.2 checkout.

The MPEG-PS (GB/T 28181, stream_type 0x80) mapping will come separately.

0002 adds libavformat/rtpdec_svac3.c, registered for SVAC3/90000: the
payload format OpenIPC cameras send, RFC 7798's shape on SVAC3's two-byte
NAL header (single NAL unit packets, fragmentation units of type 28),
with sprop-sps/sprop-pps from the SDP as extradata.

CI applies the patches in order, builds FFmpeg with the RTP/RTSP
demuxers, and holds every corpus stream sent through local UDP to the
same golden framemd5 as the raw demuxer, at 1200 bytes and at 300 so
every patch is fragmented.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add SVAC 3.0 RTP/RTSP reception and end-to-end checks

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add SVAC 3.0 RTP depacketization so FFmpeg can decode compatible camera streams over RTSP.
• Test corpus streams over local RTP against golden frame hashes, including forced fragmentation.
• Document the camera payload mapping and apply both FFmpeg patches in order.
Diagram

graph TD
  SDP["RTSP SDP"] --> RTP["RTP payload"] --> Kind{"NAL type?"} --> Single["Single NAL"] --> Parser["SVAC3 parser"] --> Decoder["libsvac3d decoder"]
  Kind --> FU["FU reassembly"] --> Parser
  SDP --> Decoder
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Extend the HEVC RTP handler
  • ➕ Could share more RFC 7798-style packet handling.
  • ➖ HEVC-specific header layout, fragmentation type, and SDP handling would need parameterization, coupling two payload formats.

Recommendation: Keep the dedicated SVAC3 handler: it isolates the camera-specific, non-standard mapping while reusing FFmpeg's existing fragment helper. Generalizing the HEVC handler is unlikely to simplify this single-format addition.

Files changed (5) +427 / -9

Enhancement (1) +230 / -0
0002-avformat-rtpdec-add-an-RTP-depacketizer-for-SVAC-3.0.patchRegister an SVAC3 RTP depacketizer in FFmpeg +230/-0

Register an SVAC3 RTP depacketizer in FFmpeg

• Adds a dynamic SVAC3 payload handler for complete NAL units and type-28 fragmentation units, producing Annex-B packets for the existing parser. It also converts SDP sprop-sps and sprop-pps values into decoder extradata and rejects malformed packet headers.

ffmpeg/0002-avformat-rtpdec-add-an-RTP-depacketizer-for-SVAC-3.0.patch

Tests (2) +154 / -0
rtp_check.shCheck RTP-decoded corpus streams against golden hashes +45/-0

Check RTP-decoded corpus streams against golden hashes

• Starts a local UDP sender and SDP-based FFmpeg receiver for each corpus stream, then compares decoded frame hashes with the existing golden output. Accepts a packet-size argument to exercise fragmentation.

ffmpeg/rtp_check.sh

rtp_send.pyGenerate SVAC3 RTP packets and receiver SDP +109/-0

Generate SVAC3 RTP packets and receiver SDP

• Splits Annex-B streams into NAL units and access units, emits complete or fragmented RTP payloads, and writes SDP with encoded SPS/PPS. Paces transmission to the local test receiver.

ffmpeg/rtp_send.py

Documentation (1) +30 / -5
README.mdDocument patch order and the SVAC3 RTP mapping +30/-5

Document patch order and the SVAC3 RTP mapping

• Explains how to apply both FFmpeg patches, run the RTP check, and interpret the OpenIPC single-NAL, fragmentation, marker, and SDP conventions.

ffmpeg/README.md

Other (1) +13 / -4
ci.ymlBuild network-enabled FFmpeg and verify RTP decoding +13/-4

Build network-enabled FFmpeg and verify RTP decoding

• Applies FFmpeg patches sequentially, checking each against the resulting tree. Enables RTP/RTSP reception in the minimal build and runs golden-hash RTP checks at 1200- and 300-byte packet sizes.

.github/workflows/ci.yml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Separate SDP lines lose a parameter set ✓ Resolved
Description
svac3_parse_sdp_line() commits extradata when either sps_size or pps_size is present, then
frees both buffers. If a sender supplies sprop-sps and sprop-pps on separate fmtp lines, the
second line replaces the first line’s data, leaving the decoder without both parameter sets.
Code

ffmpeg/0002-avformat-rtpdec-add-an-RTP-depacketizer-for-SVAC-3.0.patch[R149-150]

++    if (ret < 0 || !(data->sps_size + data->pps_size))
++        return ret;
Evidence
Each SDP attribute populates a different buffer, but the nonzero-sum check allows either buffer
alone to be copied and both to be freed. The decoder requires SPS before PPS and both before a
picture header; the test sender always puts both attributes on one line.

ffmpeg/0002-avformat-rtpdec-add-an-RTP-depacketizer-for-SVAC-3.0.patch[119-129]
ffmpeg/0002-avformat-rtpdec-add-an-RTP-depacketizer-for-SVAC-3.0.patch[147-160]
source/svac3/svac3_hls.c[719-735]
ffmpeg/rtp_send.py[78-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Separate fmtp lines cause the first parameter set to be committed and discarded before the second arrives.
## Fix Focus Areas
- ffmpeg/0002-avformat-rtpdec-add-an-RTP-depacketizer-for-SVAC-3.0.patch[147-160]
## Recommended Fix
Keep a parameter set in the payload context until both SPS and PPS are available; only then construct combined Annex-B extradata. Add a test using separate fmtp lines.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Test sender mislabels access units ✓ Resolved
Description
access_units() starts a new unit at SPS, security parameter sets, or selected picture headers, but
not at PPS, SEI, or extension NALs following a patch. On streams with those boundaries,
rtp_send.py assigns the next unit the previous unit’s timestamp and puts the marker on the wrong
last packet, while frame-hash checks cannot detect that timing error.
Code

ffmpeg/rtp_send.py[R47-49]

+        starts = t in (7, 9) or (t == 3 and prev not in (7, 8, 9))
+        if starts and au and prev not in (9,):
+            yield au
Evidence
The sender’s boundary condition omits types 5, 6, and 8, which the existing SVAC3 parser explicitly
treats as access-unit starters after patches. Sender grouping determines both the timestamp and
which packet receives the marker.

ffmpeg/rtp_send.py[41-54]
ffmpeg/rtp_send.py[93-100]
ffmpeg/0001-avcodec-add-SVAC-3.0-decoding-via-libsvac3d.patch[443-450]
ffmpeg/0001-avcodec-add-SVAC-3.0-decoding-via-libsvac3d.patch[504-525]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The RTP test sender groups some NALs with the preceding access unit despite the SVAC3 parser treating them as the start of a new one.
## Fix Focus Areas
- ffmpeg/rtp_send.py[41-54]
- ffmpeg/rtp_send.py[93-104]
## Recommended Fix
Track whether the current unit has seen a patch and start a new unit when any NAL type recognized by the SVAC3 parser as an access-unit starter follows it. Test the resulting timestamps and marker placement.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. A sender error can stall the RTP check ✓ Resolved
Description
rtp_check.sh waits for the readiness line without checking whether its background sender has
exited. If a corpus stream lacks an SPS or PPS, rtp_send.py raises during its next() lookup
before writing that line, so the check waits indefinitely instead of failing.
Code

ffmpeg/rtp_check.sh[28]

+  until grep -q sdp "$tmp/ready" 2>/dev/null; do sleep 0.1; done
Evidence
The sender performs mandatory SPS/PPS lookups before printing sdp; the shell loop tests only for
that output and has neither a child-status check nor a deadline.

ffmpeg/rtp_check.sh[23-28]
ffmpeg/rtp_send.py[74-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A sender failure before SDP readiness leaves the test checker waiting forever.
## Fix Focus Areas
- ffmpeg/rtp_check.sh[23-28]
- ffmpeg/rtp_send.py[74-87]
## Recommended Fix
While waiting for readiness, check whether the sender is still running and enforce a finite timeout. On failure, collect its exit status and terminate the check with an error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread ffmpeg/rtp_send.py Resolved
Comment thread ffmpeg/rtp_check.sh Resolved
- sprop-sps and sprop-pps on fmtp lines of their own: the first line's
  set was committed and freed before the second arrived, so the decoder
  had only one. Both are kept until close and the extradata rebuilt from
  both on every line. CI now sends them that way, with the first access
  unit's in-band sets removed so the first picture depends on the SDP;
  the old depacketizer loses the first GOP there (30 of 40 pictures).
- rtp_send.py groups access units by the parser's rule
  (ff_svac3_nal_starts_au after a patch), so timestamps and markers
  follow the pictures; the unit count equals the golden picture count on
  every corpus stream.
- rtp_check.sh fails instead of hanging when the sender dies, or does
  not write its SDP within ten seconds.
@widgetii

Copy link
Copy Markdown
Member Author

Review addressed in 684cbe4.

  1. Separate fmtp lines. Fixed. Both parameter sets are now kept until close, and the extradata is rebuilt from both on every fmtp line. CI covers it with rtp_check.sh ffbuild/ffmpeg 1200 split, which puts the sets on lines of their own and removes the first access unit's in-band copies, so the first picture decodes only from the SDP. All 8 streams are bit-exact. As a negative control, the previous depacketizer gives 30 of 40 pictures there and the check fails.
  2. Access-unit grouping. Fixed. rtp_send.py now uses the parser's own rule (a starter after a patch, as in ff_svac3_nal_starts_au), and its unit count equals the golden picture count on every corpus stream.
  3. Sender failure. Fixed. The readiness wait checks that the sender is alive, gives up after 10 s, and fails the check.

@widgetii
widgetii merged commit 545c443 into master Oct 29, 2026
6 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.

1 participant