通过 RTP TCP 流解码 h264 时出现问题

Problem decoding h264 over RTP TCP stream

我正在尝试从我的对讲机 Hikvision DS-KH8350-WTE1 通过 TCP 接收编码为 h264 的 RTP 流。通过逆向工程,我能够复制 iPhone 上的 Hikvision 原始软件 Hik-Connect 和 MacOS 上的 iVMS-4200 连接和协商流媒体的方式。现在我得到了与原始应用程序完全相同的流 - 通过 Wireshark 验证。现在我需要“理解”流。我知道它是 RTP,因为我检查了 iVMS-4200 如何在 MacOS 上使用 /usr/bin/sample 使用它。产量:

  ! :               2 CStreamConvert::InputData(void*, int)  (in libOpenNetStream.dylib) + 52  [0x11ff7c7a6]
+     ! :                 2 SYSTRANS_InputData  (in libSystemTransform.dylib) + 153  [0x114f917f2]
+     ! :                   1 CRTPDemux::ProcessH264(unsigned char*, unsigned int, unsigned int, unsigned int)  (in libSystemTransform.dylib) + 302  [0x114fa2c04]
+     ! :                   | 1 CRTPDemux::AddAVCStartCode()  (in libSystemTransform.dylib) + 47  [0x114fa40f1]
+     ! :                   1 CRTPDemux::ProcessH264(unsigned char*, unsigned int, unsigned int, unsigned int)  (in libSystemTransform.dylib) + 476  [0x114fa2cb2]
+     ! :                     1 CRTPDemux::ProcessVideoFrame(unsigned char*, unsigned int, unsigned int)  (in libSystemTransform.dylib) + 1339  [0x114fa29b3]
+     ! :                       1 CMPEG2PSPack::InputData(unsigned char*, unsigned int, FRAME_INFO*)  (in libSystemTransform.dylib) + 228  [0x114f961d6]
+     ! :                         1 CMPEG2PSPack::PackH264Frame(unsigned char*, unsigned int, FRAME_INFO*)  (in libSystemTransform.dylib) + 238  [0x114f972fe]
+     ! :                           1 CMPEG2PSPack::FindAVCStartCode(unsigned char*, unsigned int)  (in libSystemTransform.dylib) + 23  [0x114f97561]`

我可以用 lldb 捕获它,并看到到达的数据包数据与我描述的格式一样有意义。

数据包签名如下:

0x24 0x02 0x05 0x85 0x80 0x60 0x01 0x57 0x00 0x00 0x00 0x02 0x00 0x00 0x27 0xde 0x0d 0x80 0x60 0x37 0x94 0x71 0xe3 0x97 0x10 0x77 0x20 0x2c 0x51 | 0x7c 0x85 0xb8 0x00 00 00 00 01 65 0xb8 0x0 0x0 0xa 0x35 ...

0x24 0x02 0x05 0x85 0x80 0x60 0x01 0x58 0x00 0x00 0x00 0x02 0x00 0x00 0x27 0xde 0xd 0x80 0x60 0x37 0x95 0x71 0xe3 0x97 0x10 0x77 0x20 0x2c 0x51 | 0x7c 0x05 0x15 0xac ...

0x24 0x02 0x5 0x85 0x80 0x60 0x01 0x59 0x00 0x0 0x00 0x02 0x00 00x0 0x27 0xde 0xd 0x80 0x60 0x37 0x96 0x71 0xe3 0x97 0x10 0x77 0x20 0x2c 0x51 | 0x7c 0x05 0x5d 0x00 ...

通过对原软件进行逆向,我发现0x7c85是一个关键帧。正版软件处理中的 0x7c85 字节确实被 h264 00 00 00 01 65 关键帧 NALU 取代。那是 h264 appendix-B 格式。 0x7c05 数据包总是跟随并且是关键帧的剩余有效载荷。在处理过程中没有添加 NALU(0x7c05 被剥离,其余字节被复制)。 None 0x7cXX 之前的字节使其成为 mp4 记录(这是有道理的,因为它是 RTP 协议,尽管我不确定它是否完全是 RTP 标准或海康威视有一些自定义内容)。

如果您在 Header 中仔细观察,有 2 个单独的字节指示顺序始终匹配,所以我确定没有发生丢包。

我还观察到非关键帧作为 0x7c81 到达并转换为 00 00 00 01 61 NALU,但我现在只想关注单个关键帧。主要是因为如果我用原始软件录制电影,它总是以 00 00 00 01 65 关键帧开始(这显然是有道理的)。

为了获得一个工作的 mp4,我决定复制粘贴一个真正的 iVMS-4200 记录的 mp4 header(从这个意义上说,这是 mp4 文件中第一帧 NALU 00 00 00 01 65 之前的每个字节) .我知道分辨率将与实际的摄像机镜头相匹配。通过等待关键帧的策略,将 0x7c85 替换为 00 00 00 01 65 NALU 并附加剩余的字节,或者仅在 0x7c05 情况下附加字节,我似乎确实得到了最终可以工作的东西. 当我尝试 ffplay 自定义制作的 mp4 结果时,我确实得到了一些东西(有点想象力,实际上是相机鱼眼图像形成),但显然存在问题。

似乎大约第 3-4 个 0x7c05 数据包(因为失败的数据包在每个 运行 上都不同),当我最终复制字节时,h264 流不正确。仅通过 eye-inspecting 个字节,我没有发现任何异常。

这是十进制偏移量 750 附近的失败数据包(我知道它在这个地方附近,因为我一直在剥离字节以查看在它中断之前是否还有相同数量的帧到达)。 此外,我确实使用 lldb 从原始软件中转储了这些字节,从而排除了我自己的 python 实现。我 运行 遇到与原始数据包完全相同的问题。

我使用的 mp4 header 应该可以工作(因为即使我操纵帧数并只留下第一个关键帧,它也适用于原始录音)。 如果我错了请纠正我,但是将其转换为 MPEG2-PS 的阶段(iVMS-4200 做而我不做)应该是完全可选的,不应该与我的问题有关。

更新: 我走了设置记录的路径,然后才转储原始 iVMS-4200 数据包。我将录制的电影编辑为仅包含感兴趣的关键帧并且它有效。我发现了差异,但我无法解释它们在哪里: 不知何故 00 00 01 E0 13 FA 88 00 02 FF FF 被插入到真正的录音中(那是第 4 个数据包),但我不知道这个字节串是如何生成的以及它的目的是什么。 当我修复第一个差异时,下一个是: 图案很醒目。但是 00 00 01 E0 13 FA 88 00 02 FF FF 实际上是什么?还有为什么插在18 03 25 10&2F F4 80 FC
之后 00 00 01 E0 签名表明这些是分组基本流 (PES) headers

选择 mp4 容器毕竟不是一个好的选择。事实证明,RTP 基本上会产生原始的 h264 流。为了检查其结构,我将真正的 mp4 录音转换为 .264,如下所示:

ffmpeg -i recording.mp4 -codec copy recording.264

它本质上是一个 PPS (00 00 00 01 67) 和 SPS 00 00 00 01 68,后面是我在流中获得的帧数据 NALU。

Raw h264 原来是一种更简单的目标结构,我不必再处理那些打包的基本流 (PES) headers。这会产生正确的图像。在我的例子中,我只是从 recording.264 中获取了原始录音使用的 PPS 和 SPS 设置。这肯定可以以某种方式动态解决,但我没有打扰。