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

Some IP cameras (e.g. CostarHD) have been observed to emit single
fragment RTP FU-A packets, with start and end bit both set, for
sufficiently small H.264 P-frames. Rather than emit an error,
workaround such non-compliant frames.
This commit is contained in:
Chris Hiszpanski
2024-12-04 14:21:09 -08:00
committed by GitHub
parent d7efbd0a48
commit 44aaa0977a
2 changed files with 46 additions and 4 deletions

View File

@@ -90,10 +90,6 @@ 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:])
@@ -101,6 +97,20 @@ func (d *Decoder) decodeNALUs(pkt *rtp.Packet) ([][]byte, error) {
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
}

View File

@@ -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()