FFmpeg开发笔记(三十三)分析ZLMediaKit对H.264流的插帧操作

《FFmpeg开发实战:从零基础到短视频上线》一书的"3.4.3 把原始的H264文件封装为MP4格式"介绍了如何把H.264裸流封装为MP4文件。那么在网络上传输的H.264裸流是怎样被接收端获取视频格式的呢?前文指出H.264流必定以"SPS帧→PPS帧→IDR帧"开头,接下来就来验证是否确实如此。

这里用到了雷霄骅雷神写的H264分析器,在此向雷神致敬,雷神10年前写的小程序至今仍然好用。打开H264分析器,该软件的初始界面如下图所示:

单击文件路径栏右边的打开按钮,在弹出的文件对话框中选择某个H.264裸流文件,再单击界面右下角的开始按钮,分析器便开始分析H264文件的内容格式,分析后的结果界面如下图所示:

从分析结果可见,H.264裸流的开头三帧果然是"SPS帧→PPS帧→IDR帧"。单击列表中的某个帧,界面右侧会显示该帧的详细字段信息。

当然,分析器只能读取H.264裸流文件。倘若让分析器读取MP4文件,就无法正常读出各帧信息。那么流媒体服务器又是怎么把MP4文件转化为H.264裸流的呢?

以ZLMediaKit为例,它在向推流序列插入I帧时做了特殊处理,一旦出现I帧,就自动插入SPS与PPS等配置帧。具体代码在ZLMediaKit框架的ext-codec/H264.cpp,查看该源码的H264Track::inputFrame_l函数,找到以下的代码片段,可见程序在判断关键帧之后调用了insertConfigFrame函数。

复制代码
// 判断是否是I帧, 并且如果是,那判断前面是否插入过config帧, 如果插入过就不插入了
if (frame->keyFrame() && !_latest_is_config_frame) {
    insertConfigFrame(frame); // 插入SPS帧和PPS帧
}
if(!frame->dropAble()){
    _latest_is_config_frame = false;
}
ret = VideoTrack::inputFrame(frame);

找到insertConfigFrame函数的定义代码如下,果然函数内容依次插入了SPS帧和PPS帧:

复制代码
// 插入SPS帧和PPS帧
void H264Track::insertConfigFrame(const Frame::Ptr &frame) {
    if (!_sps.empty()) { // 插入SPS帧
        auto spsFrame = FrameImp::create<H264Frame>();
        spsFrame->_prefix_size = 4;
        spsFrame->_buffer.assign("\x00\x00\x00\x01", 4);
        spsFrame->_buffer.append(_sps);
        spsFrame->_dts = frame->dts();
        spsFrame->setIndex(frame->getIndex());
        VideoTrack::inputFrame(spsFrame);
    }
    if (!_pps.empty()) { // 插入PPS帧
        auto ppsFrame = FrameImp::create<H264Frame>();
        ppsFrame->_prefix_size = 4;
        ppsFrame->_buffer.assign("\x00\x00\x00\x01", 4);
        ppsFrame->_buffer.append(_pps);
        ppsFrame->_dts = frame->dts();
        ppsFrame->setIndex(frame->getIndex());
        VideoTrack::inputFrame(ppsFrame);
    }
}

由此可见,ZLMediaKit在每个关键帧前面都额外插入了SPS帧和PPS帧,确保H.264裸流维持着形如"SPS帧→PPS帧→IDR帧"的队形。如果不添加SPS和PPS,客户端在拉流时会报错如下:

复制代码
[NULL @ 0000022ed7782540] non-existing PPS 0 referenced

只有加上SPS与PPS,客户端才能正常拉流解析数据,才能正常渲染视频画面。

更多详细的FFmpeg开发知识参见《FFmpeg开发实战:从零基础到短视频上线》一书。

相关推荐
VOOHU-沃虎16 小时前
PoE+音频一体化接口设计:从电源变压器到XLR卡侬座的完整链路
音视频
“码”力全开18 小时前
解密企业级智能视频中台:基于 Docker 与边缘计算的 GB28181/RTSP 异构架构设计(支持源码交付)
docker·音视频·边缘计算
潜创微科技18 小时前
QCW5007+QCW5004 | HDMI 1.3 无线投屏芯片方案空旷 150 米支持穿墙传输
音视频
MemoriKu19 小时前
Flutter 相册 APP 视频模态稳定化实战:从视频抽帧、Embedding 元数据到 Android 真机启动修复
android·开发语言·前端·flutter·架构·音视频·embedding
EasyDSS19 小时前
视频直播点播/高清点播/音视频点播/云点播/云直播EasyDSS一站式音视频平台助力智慧校园智能化建设
音视频
johnny23319 小时前
视频创作工具:OpenCut、HyperFrames、social-auto-upload、OpenStoryline、ArcReel
音视频
换个昵称都难19 小时前
WebRTC 视频RTP 优化模块
音视频·webrtc
EasyGBS20 小时前
国标GB28181成安防刚需,国标GB28181视频平台EasyGBS如何成为视频上云最优解?
音视频
luoyayun36120 小时前
Qt/QML 音频波形图模块实现:从 PCM 数据到可缩放波形
qt·音视频·波形图绘制
hz5678920 小时前
医院LIS系统如何对接视频会议系统?远程诊疗协同方案详解
音视频·实时音视频·信息与通信