Skip to content

ffmpeg: receive a GB/T 28181 program stream over RTP and RTSP - #16

Merged
widgetii merged 1 commit into
masterfrom
ffmpeg-rtp-ps
Oct 29, 2026
Merged

widgetii merged 1 commit into
masterfrom
ffmpeg-rtp-ps

Conversation

@widgetii

Copy link
Copy Markdown
Member

0004 adds an RTP depacketizer for PS/90000, the program stream over RTP
that GB/T 28181 uses. Until now FFmpeg had none: a camera's
?payload=ps stream opened as "codec none", and a capture had to be
depacketized by hand before 0003's demuxer could read it.

rtpdec_ps.c collects a picture's payloads (one RTP timestamp per
picture) and ends the picture at its marker, or when the next picture
begins without one. It reads the picture's packs. The program stream
map names the video stream and its codec (SVAC 0x80, H.264, H.265,
MPEG-4, MPEG-1/2), and that stream's PES payloads, joined, are the
packet. Audio and other streams are skipped. A picture that lost a
packet is dropped, and so is what follows until a payload opens with a
pack header.

rtp_send.py sends the corpus as PS over RTP, framed by ps_write.py
(now with pack() per access unit). With "ps-nomarker" it leaves out the
marker bits. CI runs rtp_check.sh in both modes, at MTU 1200 and 300.

On a lab CV608 streaming SVAC3, with the sub stream in H.265:

  • ffmpeg -i "rtsp://camera/stream=0?payload=ps" decoded 300 pictures
    in 10 s over TCP and 300 over UDP, with no warnings;
  • the H.265 sub stream decoded 302;
  • native RTP and PS of the same encoder, received at the same time,
    gave 360 of 360 identical pictures, both ways round (native over UDP
    with PS over TCP, and the reverse).

Verification

  • rtp_check.sh in ps mode at MTU 1200 and 300: every corpus stream equals its golden framemd5.
  • ps-nomarker passed on 7 of 8 streams locally. The eighth, the largest (synth 1080p), lost UDP packets on a heavily loaded workstation (the decoder reported "26 packet(s) lost"). The native-payload check lost frames on the same stream at MTU 1200 on that machine, and that path is unchanged by this PR. CI runs all of these on a clean runner.
  • Lab camera: see above. The camera majestic was the code of majestic's ?payload=ps support, as merged.

0004 adds an RTP depacketizer for PS/90000, the program stream over RTP
that GB/T 28181 uses. Until now FFmpeg had none: a camera's
?payload=ps stream opened as "codec none", and a capture had to be
depacketized by hand before 0003's demuxer could read it.

rtpdec_ps.c collects a picture's payloads (one RTP timestamp per
picture) and ends the picture at its marker, or when the next picture
begins without one. It reads the picture's packs. The program stream
map names the video stream and its codec (SVAC 0x80, H.264, H.265,
MPEG-4, MPEG-1/2), and that stream's PES payloads, joined, are the
packet. Audio and other streams are skipped. A picture that lost a
packet is dropped, and so is what follows until a payload opens with a
pack header.

rtp_send.py sends the corpus as PS over RTP, framed by ps_write.py
(now with pack() per access unit). With "ps-nomarker" it leaves out the
marker bits. CI runs rtp_check.sh in both modes, at MTU 1200 and 300.

On a lab CV608 streaming SVAC3, with the sub stream in H.265:
- ffmpeg -i "rtsp://camera/stream=0?payload=ps" decoded 300 pictures
  in 10 s over TCP and 300 over UDP, with no warnings;
- the H.265 sub stream decoded 302;
- native RTP and PS of the same encoder, received at the same time,
  gave 360 of 360 identical pictures, both ways round (native over UDP
  with PS over TCP, and the reverse).
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add FFmpeg RTP depacketization for GB/T 28181 program streams

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Receive PS/90000 video over RTP and RTSP without manually extracting program-stream payloads.
• Assemble pictures, select mapped video PES data, and recover after RTP packet loss.
• Test marked and markerless streams against golden frame hashes.
Diagram

graph TD
  SDP["PS/90000 SDP"] --> Handler["RTP handler"] --> Buffer["Picture buffer"] --> Complete{"Picture complete?"} --> Map["Program map"] --> PES["Video PES"] --> Stream["AVStream packet"]
  Buffer -->|packet gap| Recovery["Resync at pack"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Feed reassembled PS into the existing MPEG demuxer
  • ➕ Reuses FFmpeg's established program-stream parsing and stream discovery.
  • ➖ Needs an internal bridge from RTP payloads to a demuxer and additional handling for picture boundaries, timestamps, and loss.
  • ➖ Could expose multiple PS streams where this RTP handler is expected to return one video AVStream.

Recommendation: The dedicated RTP handler fits the picture-per-timestamp transport and explicit loss recovery. Reviewers should weigh that fit against the maintenance cost of parsing PS maps and PES headers separately from FFmpeg's MPEG demuxer.

Files changed (6) +449 / -22

Enhancement (1) +355 / -0
0004-avformat-rtpdec-add-a-depacketizer-for-GB-T-28181-s-program-stream.patchAdd and register FFmpeg's PS/90000 RTP handler +355/-0

Add and register FFmpeg's PS/90000 RTP handler

• The FFmpeg patch registers a new handler that assembles pictures by RTP timestamp and marker, discovers supported video codecs from program-stream maps, and joins video PES payloads. It skips other streams and drops damaged pictures until a pack-header boundary.

ffmpeg/0004-avformat-rtpdec-add-a-depacketizer-for-GB-T-28181-s-program-stream.patch

Refactor (1) +19 / -13
ps_write.pyExpose per-access-unit program-stream packing +19/-13

Expose per-access-unit program-stream packing

• Extracts pack generation into a reusable function while preserving file-writing behavior. The RTP sender uses it to frame each access unit as a program-stream pack.

ffmpeg/ps_write.py

Tests (3) +55 / -4
ci.ymlRun PS-over-RTP golden-frame checks in CI +6/-0

Run PS-over-RTP golden-frame checks in CI

• Adds checks at MTU 1200 and 300 with markers, plus an MTU 1200 check without markers. Each compares decoded frames with the existing golden hashes.

.github/workflows/ci.yml

rtp_check.shAccept PS-over-RTP check modes +3/-2

Accept PS-over-RTP check modes

• Updates the usage text for PS and markerless PS modes and reports the selected mode in check output.

ffmpeg/rtp_check.sh

rtp_send.pySend corpus access units as PS/90000 RTP +46/-2

Send corpus access units as PS/90000 RTP

• Adds SDP and UDP RTP generation for packed program-stream access units, with MTU fragmentation and optional marker bits. In markerless mode, a final new-timestamp payload closes the last picture.

ffmpeg/rtp_send.py

Documentation (1) +20 / -5
README.mdDocument PS/90000 receiving and checks +20/-5

Document PS/90000 receiving and checks

• Lists the fourth FFmpeg patch and explains its RTP framing, codec mapping, loss behavior, and RTSP usage. Documents the new marked and markerless test modes.

ffmpeg/README.md

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

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

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

@widgetii
widgetii merged commit c98d6ac 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