kinesis video streams 的 getmedia 响应是流式 mkv cluster(非完整文件),libavformat 因依赖 seekable 输入、严格 probe 和完整 ebml 头而无法直接解析;须用 sdk 内置 streamparser 解析帧,以 pts × timecode_scale.count() / 1000000 转毫秒时间戳,并按 frame_flag_codec_private_data 提取 sps/pps 初始化解码器。

直接解析 Kinesis Video Streams 的原始数据流报文(即从 GetMedia 或 GetMediaForFragmentList 返回的字节流)不是“解析 JSON 响应”那种层级的操作——你拿到的是一个合法但结构紧凑的 Matroska(MKV)容器流,里面混着 H.264/H.265 帧、时间戳、TrackEntry、Cluster 等二进制块。C++ 里没有开箱即用的“Kinesis 报文解析器”,必须靠 MKV 解析 + 帧提取 + 时间码对齐三步协同完成。
为什么不能直接用 libavformat 解析 GetMedia 返回流
Amazon Kinesis Video Streams 的 GetMedia 响应体是 **带 chunked-encoding 的 HTTP 流式响应**,头部含 x-amzn-fragment-length、x-amzn-fragment-timecode 等自定义 header,且实际 payload 是连续拼接的 MKV Cluster(不是完整 MKV 文件)。libavformat 默认期望一个 seekable、有完整 EBML Header + Segment 的文件,直接喂它会卡在 avformat_open_input 阶段,报错类似:
Invalid data found when processing input
或静默失败(nb_streams == 0)。关键原因有三个:
-
avformat_open_input尝试读取前 8KB 做 probe,但 Kinesis 流首帧 Cluster 可能延迟到达,导致 probe 超时或误判 - Kinesis 不保证每个 Cluster 都带完整的
SimpleBlock头部字段(如 missingTimecode字段时复用上一帧),而 FFmpeg 对缺失字段容忍度低 - 服务端可能在 Cluster 内部做 fragment boundary 对齐,导致
av_read_frame返回不完整帧或跨 Cluster 的 AVPacket
正确做法:用 matroska_parser.h + 自定义 FrameReader
Kinesis C++ Producer SDK 本身带了一套轻量级 MKV 解析逻辑(位于 src/common/src/StreamParser.cpp 和 include/com/amazonaws/kinesis/video/common/StreamParser.h),它不依赖 FFmpeg,专为流式场景设计。你应该复用这部分,而不是重造轮子。核心步骤是:
- 继承
StreamParser,重写handleFrame回调,在这里拿到原始Frame结构体(含frameFlags、duration、pts、dts、flags) - 用
StreamDefinition中的timecode_scale(默认 1ms)把pts转成毫秒级时间戳:timestampMs = pts * timecode_scale.count() / 1000000 - 注意区分
frameFlags & FRAME_FLAG_KEY_FRAME—— Kinesis 的 I 帧不一定等于 MKV 的SimpleBlockflag=0x80,要以这个位为准 - 不要假设帧顺序和 Cluster 顺序严格一致;Kinesis 允许服务端 re-order,
pts才是唯一可信的时间依据
常见坑:timecode_scale 和 fragment_duration 的单位混淆
timecode_scale 是 MKV 规范里的标度因子(单位纳秒),但它在 Kinesis SDK 中被封装为 duration<uint64_t milli></uint64_t> 类型,默认值是 1ms,即 1000000ns。而 fragment_duration 是秒级 uint64_t(比如设为 2 表示“约 2 秒一个 fragment”)。这两者数值上容易看岔:
- 如果你手动解析 EBML Header,看到
TimecodeScale = 1000000,这是对的,对应 1ms - 但如果你在
StreamDefinition里把timecode_scale设成1(以为是 1ms),实际会被解释为 1 纳秒 → 导致所有pts放大 100 万倍 → 时间戳爆炸 - 同理,
fragment_duration = 2是 2 秒,不是 2 毫秒;它只影响服务端 fragment 切分策略,不影响客户端解析逻辑
最稳妥的方式是完全信任 SDK 提供的 Frame.pts 和 StreamDefinition.timecode_scale 成员,不做单位换算猜测。
硬解 H.264 帧时如何处理 AUD 和 SPS/PPS
Kinesis 流中每个关键帧(I-frame)前必带 SPS/PPS,但它们不是独立 packet,而是作为 MKV Block 的一部分嵌在 Cluster 里。SDK 的 Frame 结构体已做预处理:若 frameFlags & FRAME_FLAG_CODEC_PRIVATE_DATA 为真,则该帧数据开头就是 Annex-B 格式的 SPS+PPS(含 0x00000001 起始码),后续帧则只有 NALU 数据。你需要:
- 首次收到带
FRAME_FLAG_CODEC_PRIVATE_DATA的帧时,用h264_get_sps_pps_from_annexb(或 OpenH264 的OBSOLETE_DECODE_FIRST_NAL)提取参数集并初始化解码器 - 后续帧直接送入解码器,但注意:Kinesis 不保证每个帧都是完整 NALU;有些帧末尾可能被截断(尤其网络抖动时),需检查
Frame.size是否匹配预期 NALU 长度 - AUD(Access Unit Delimiter)NALU(0x09)在 Kinesis 流中通常存在,但可安全跳过;它不携带图像数据,仅用于边界对齐
绕过这层处理、直接把整个 Frame.frameData 当作裸 H.264 送进 FFmpeg avcodec_send_packet,大概率触发 Invalid data found when processing input。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











