Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rtph264: accept non-compliant single FU-A frames #649

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions pkg/format/rtph264/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,27 @@ func (d *Decoder) decodeNALUs(pkt *rtp.Packet) ([][]byte, error) {
if start == 1 {
d.resetFragments()

if end != 0 {
return nil, fmt.Errorf("invalid FU-A packet (can't contain both a start and end bit)")
}

nri := (pkt.Payload[0] >> 5) & 0x03
typ := pkt.Payload[1] & 0x1F
d.fragmentsSize = len(pkt.Payload[1:])
d.fragments = append(d.fragments, []byte{(nri << 5) | typ}, pkt.Payload[2:])
d.fragmentNextSeqNum = pkt.SequenceNumber + 1
d.firstPacketReceived = true

// RFC 6184 clearly states:
//
// A fragmented NAL unit MUST NOT be transmitted in one FU; that is, the
// Start bit and End bit MUST NOT both be set to one in the same FU
// header.
//
// However, some vendors camera (e.g. CostarHD) have been observed to nevertheless
// emit one fragmented NAL unit for sufficiently small P-frames.
if end != 0 {
nalus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)}
d.resetFragments()
break
}

return nil, ErrMorePacketsNeeded
}

Expand Down
32 changes: 32 additions & 0 deletions pkg/format/rtph264/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,38 @@ func TestDecodeCorruptedFragment(t *testing.T) {
require.Equal(t, [][]byte{{0x01, 0x00}}, nalus)
}

func TestDecodeNoncompliantFragment(t *testing.T) {
d := &Decoder{}
err := d.Init()
require.NoError(t, err)

nalus, err := d.Decode(&rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 18853,
Timestamp: 1731630255,
SSRC: 0x466b0000,
},

// FU-A with both start and end bit intentionally set
// While not compliant with RFC 6184, IP cameras from some vendors
// (e.g. CostarHD) have been observed to produce such FU-A payloads for
// sufficiently small P-frames.
Payload: mergeBytes(
[]byte{
0x3c, // FU indicator
0xc1, // FU header (start and end bit both intentionally set)
0xe7, 0x00, // DON
0xca, 0xfe, // Payload
},
),
})
require.NoError(t, err)
require.Equal(t, [][]byte{{0x21, 0xe7, 0x00, 0xca, 0xfe}}, nalus)
}

func TestDecodeSTAPAWithPadding(t *testing.T) {
d := &Decoder{}
err := d.Init()
Expand Down