diff --git a/pkg/format/rtph264/decoder.go b/pkg/format/rtph264/decoder.go index bcb46fa0..3de203a9 100644 --- a/pkg/format/rtph264/decoder.go +++ b/pkg/format/rtph264/decoder.go @@ -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 } diff --git a/pkg/format/rtph264/decoder_test.go b/pkg/format/rtph264/decoder_test.go index f276c622..fda63587 100644 --- a/pkg/format/rtph264/decoder_test.go +++ b/pkg/format/rtph264/decoder_test.go @@ -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()