Search code examples
c#h.264http-live-streamingrtpmpeg2-ts

Issue decoding H264 over RTP


I'm trying to read H264 frames from an MPEG2-TS stream (via HLS) and getting stuck, after studying the RTP rfc and ISO 13818-1 (MPEG2-TS). I've parsed the PES packets from the MPEG-TS stream, and am trying to read the RTP/H264 payload however it doesn't seem to decode correctly (and I am probably missing something). The first start marker of 00 00 00 01 is as expected, but the following byte (0xE0) seems invalid. There are also multiple start markers followed by very little data in the 188 byte packet:

single 188 byte packet:

47 41 00 30 07 50 00 00 00 00 FE 00 00 00 01 E0 00 00 80 80 05 21 00 01 00 03 00 00 00 01 09 F0 00 00 00 01 67 64 00 2A AC 2C 6A 81 E0 08 9F 97 01 6E 02 02 02 80 00 00 03 00 80 00 00 1E 42 00 00 00 01 68 EE 3C B0 00 00 00 01 06 E5 01 6C 80 00 00 00 01 65 B8 00 00 1C D9 C0 00 05 FF 6D 0B 52 D2 A2 D1 EF 11 EC FF C5 4A EC 63 C1 47 9F A7 78 36 7B 1F 30 0A 24 C8 0C 35 2D 96 BD BC 97 B5 E0 AA 40 A3 9F 3E 4A 42 C2 9B D0 43 63 6F 82 CD 94 2C 3C 7F A2 C1 A4 29 DE 73 80 5E BF 1C 1B E9 73 0B F5 FE FD BC 92 ED 11 67 8C 94 63 AB CE 40 4C 5C D2 68 DC 2D 91 CE 19 2E BB 98

I decoded the first byte of the NAL unit header after the start marker (0xE0) as:

//  forbidden_zero_bit = 1 bit (for error checking, should be 0)
//  ref_idc = 2 bits: is a reference field / frame / picture
//  unit_type = 5 bits: Specifies the NAL payload type.
var nalUnitHeader = 0xE0; // 1110 0000
var forbidden_zero = nalUnitHeader.GetBits(1, 1); // 0000 0001, 1 bit (is invalid)
var ref_idc = nalUnitHeader.GetBits(2, 2); // 0000 0011, next 2 bits
var nalUnitType = nalUnitHeader.GetBits(5, 4); // 0000 0000, next 5 bits

The spec says forbidden_zero field must always be 0, so it would appear I'm missing something. Above, GetBits() is a helper that just calculates the mask and shift offset and I confirmed it is correct unless endianness is the other way, but I checked how others do it and looks correct compared to their results.


Solution

  • It turns out it was a bug in the TsDecoder library I was using to process the transport packets in the MPEG2-TS stream, that wasn't processing the adaption_field_length properly. It was off by only 1 byte, but that ended up giving me an incorrect offset, and what I thought was the start of a NAL unit ended up actually being the unprocessed byte 00 and start of the Pes packet header 00 00 01 E0. Confusing, as it sure looks a lot like a NAL unit start marker 00 00 00 01!