如何从H264 RTP数据包中检测I / P / B帧

时间:2014-10-25 00:53:16

标签: h.264 rtsp rtp

我从RTSP流中获得了H264 RTP数据包。所以我想检测帧是否是I帧。 以下是我第一次打开流时获得的第一个数据包。所以我相信它是一个I帧(我先显示160个字节)。

packet:
00 00 00 01 67 4D 00 1F 95 A8 14 01 6E 40 00 00 00 01 68 EE 3C 80 00 00 00 01 06 E5 01 33 80 00 00 00 01 65 B8 00 00 08 52 90 9F F6 BE D6 C6 9C 3D F6 D4 2F 49 FB F7 13 F2 A9 C7 27 2D A4 75 59 6C DB FF 35 27 A4 C7 B6 E7 69 A2 E0 FB 0E FF 2D 0E E0 6F 25 43 78 BF B9 69 22 1B 24 E3 CA 60 56 44 16 6C 15 44 DA 55 29 C2 39 24 86 CE D6 75 BB E0 0C F4 F4 EC C5 76 E4 7B 59 B9 40 2D B3 ED 19 E4 1D 94 B7 54 9B B3 D0 8F 24 58 CD 3C F3 FA E0 D4 7D 88 70 0E 49 79 12 B2 14 92 BA B6 9C 3A F7 8D 13 78 6B 4C CD C0 CC C8 39 6A AC BE 3D AA 00 9A DB D2 68 70 5F C4 20 B7 5C FC 45 93 DB 00 12 9F 87 5A 66 2C B2 B8 E7 63 C4 87 0B A4 AA 2E 6D AB 42 3F 02 C2 A6 F9 41 E5 FE 80 64 49 14 38 3D 52 4B F6 B2 E7 53 DD 3E F6 BB A8 EB 13 23 BB 71 B1 C9 90 06 92 3E 5F 15 F2 C0 39 43 EA 24 5A 86 AE 11 27 D4 C5 4B 5C CD 6C 90 2B 44 80 18 76 95 6E 16 DF 5D 86 49 25 5A B6 66 23 E6 40 D4 25 6B CE A2 4C EE 13 DD 7B 88 FF A0 64 EC 33 44 B1 DC B7 0B 89 5B 8F 85 68 3C 65 3E 55 0F 41 4B 32 C9 C8 56 78 1A 15 14 8C C7 F5 17 40 D4 EC BC 5B 62 8A 24 66 6A C3 7E 3B DB 44 A8 EC D8 EE 37 E0 DE ...........

然后我使用下面的代码来确定框架

public static bool isH264iFrame(byte[] paket)
    {
        int RTPHeaderBytes = 0;

        int fragment_type = paket[RTPHeaderBytes + 0] & 0x1F;
        int nal_type = paket[RTPHeaderBytes + 1] & 0x1F;
        int start_bit = paket[RTPHeaderBytes + 1] & 0x80;

        if (((fragment_type == 28 || fragment_type == 29) && nal_type == 5 && start_bit == 128) || fragment_type == 5)
        {
            return true;
        }

        return false;
   }

我的问题是我无法完全知道RTPHeaderByte的价值。在这种情况下,我的数据包始终以“00 00 00 01”开头。

由于 Tien Vo

2 个答案:

答案 0 :(得分:3)

您必须解析有效负载。看到SO回答Possible Locations for Sequence/Picture Parameter Set(s) for H.264 Stream。对于IDR,所有VCL NALU都是类型5.对于B / P,您需要解析exp-golmb编码数据以找到切片类型。

答案 1 :(得分:0)

实际上,这看起来不对:

    int fragment_type = paket[RTPHeaderBytes + 0] & 0x1F;
    int nal_type = paket[RTPHeaderBytes + 1] & 0x1F;
    int start_bit = paket[RTPHeaderBytes + 1] & 0x80;

首先是 NAL 类型,然后是其他内容,NAL 类型字节的第 7 位始终为 0。

事实上,您可以简单地搜索两个或三个零后跟一个 1,这就是 NAL 的标记。 NAL紧随其后。我目前不清楚 2 个零和 3 个零之间有什么区别。

因此,在您的示例中,您有以下 NAL:

00 00 00 01 67 4D 00 1F : 95 A8 14 01 6E 40 00 00
^^ ^^ ^^ ^^ ^^                              ^^ ^^
00 01 68 EE 3C 80 00 00 : 00 01 06 E5 01 33 80 00
^^ ^^ ^^          ^^ ^^   ^^ ^^ ^^             ^^
00 00 01 65 B8 00 00 08 : 52 90 9F F6 BE D6 C6 9C
^^ ^^ ^^ ^^
3D F6 D4 2F 49 FB F7 13 : F2 A9 C7 27 2D A4 75 59
.. .. .. .. .. .. .. .. : .. .. .. .. .. .. .. ..

所以这意味着你有 0x67、0x68、0x06、0x65,作为 per the link given by szatmary,你有(即我们做 type = (byte & 0x1F)):

7   Sequence parameter set                         non-VCL
8   Picture parameter set                          non-VCL
6   Supplemental enhancement information (SEI)     non-VCL
5   Coded slice of an IDR picture                  VCL

5 表示您有一个 I 帧。

查看我的一个文件,下一组 NAL 使用 0x41 或 0x01,这是非 IDR 图片的编码切片(即 B 帧)。偶尔,我会看到 5 而不是 1(即 I 帧)。默认情况下,x264 每 250 帧左右生成一个新的 I-Frame。您可以更改该参数。

因此,您的代码检测这组 NAL 是代表 I-Frame 还是另一个帧,需要搜索该帧内的所有 NAL,并找到 1(B-Frame)或 5(I-Frame)。

in.open("source-file.h264");
while(in)
{
    char marker[4];
    in.read(marker, 3);
    for(;;)
    {
        in.read(marker + 3, 1);
        if(marker[0] == 0
        && marker[1] == 0
        && marker[2] == 1)
        {
            // found one! (short one)
            break;
        }
        if(marker[0] == 0
        && marker[1] == 0
        && marker[2] == 0
        && marker[3] == 1)
        {
            // found one! (long one)
            break;
        }
    }
    in.read(marker, 1);
    type = marker[0] & 0x1F;
    if(type == 1)
    {
        return B_FRAME;
    }
    if(type == 5)
    {
        return I_FRAME;
    }
}
return NOT_FOUND;

警告:除非您的 in 文件中有一个好的辅助缓冲区,否则这段代码会很慢。这是 C++ 代码。如果缓冲区中已有数据,则应使用缓冲区中的指针或索引替换 in 文件,这肯定会非常快。

注意:如果 H.264 格式碰巧有 0x00 0x00 0x00 或 0x00 0x00 0x01 序列,请确保插入 3。也就是说,任何一个看起来都像这样:0x00 0x00 0x03 0x00 和 0x00 0x00 0x03 0x01。你可以尝试压缩纯黑框,你会看到很多那些0x03出现在NAL图片数据中。