播放器起播流程(整合 FFmpeg 实战)
从码流到画面的完整流程
以 BTV 的 H.264 / AVC + HTTP-FLV 线路为例:
- 打开输入、探测流信息:读取 FLV 中的配置记录,取得 SPS/PPS、编码类型、尺寸和时间基。
- 初始化解码器:把流参数交给解码上下文,选择软件解码或 D3D11 硬件解码。
- 持续读取视频包:视频包中的 NALU 按 AVCC 长度前缀排列,由 FFmpeg 完成解析。
- 建立解码参考状态:从直播中途接入时,需要取得有效参数和合适的随机接入点,H.264 常用 IDR。
- 解码、同步、显示:取得解码画面,根据时间戳安排呈现,再交给 Qt 渲染。
服务端缓存 Sequence Header + 最近一个可解码 GOP,可以让新观众更快取得参数和起始关键帧,从而缩短起播等待。
每一步对应的 FFmpeg 调用
| 原理步骤 | FFmpeg 对应接口或数据 |
|---|---|
| 打开直播地址 | avformat_open_input() |
| 探测编码、尺寸等信息 | avformat_find_stream_info() |
| 选择视频流 | av_find_best_stream() |
| 获取 SPS/PPS 配置记录 | stream->codecpar->extradata |
| 将流参数交给解码器 | avcodec_parameters_to_context() |
| 打开解码器 | avcodec_open2() |
| 读取压缩包 | av_read_frame() |
| 提交压缩包 | avcodec_send_packet() |
| 取得解码画面 | avcodec_receive_frame() |
| 定位后重置解码状态 | avcodec_flush_buffers() |
初始化的核心调用顺序:
1 | // 打开输入并探测流信息,取得解码所需的编码参数。 |
对于 H.264 FLV,可以把输入分成两部分理解:
1 | Sequence Header |
配置记录提供“怎么解码”,视频包提供“要解码的数据”。
AVCC 和 Annex-B 的边界解析交给 FFmpeg。应用通过统一的流参数和送包接口使用它们;软件解码和硬件解码同样使用这套接口。
BTV 中的线程与起播处理
BTV 将读取、解码和显示分配给不同线程:
1 | 解封装线程 |
项目通过 VideoDecoder 封装解码接口:
1 | // 视频线程提交压缩包,返回值交给会话层处理。 |
送包后循环取帧;取帧返回 EAGAIN 时,继续提供输入。初始化和帧重排序期间,解码器会积累所需数据,再输出画面。
正常起播持续提交视频包,由 FFmpeg 建立解码参考状态。项目已有 H.264 对照验证:保留配置数据、删除首个关键包后,画面在后续关键帧到达时恢复 GPU 解码。
视频回退软件解码,以及特定 AV1 数据错误恢复时,会开启关键包等待:
1 | // 恢复期间等待封装层标记的关键包,跳过中间非关键包。 |
AV_PKT_FLAG_KEY 适合应用层进行关键包筛选;H.264 IDR 的精确识别依据是 NALU type 5。
定位、参数变化与起播速度
定位与重新加载
本地 seek 成功后,项目依次处理:
- 清空音视频包队列。
- 通知音视频解码线程执行 flush。
- 重置同步时钟。
- 从定位后的数据继续解码,丢弃目标时间之前的画面。
时移回放切换也通过队列清理和解码状态刷新衔接时间线。重新加载输入时,则关闭旧会话并重新建立解码上下文。
推流参数变化
推流端更新 H.264 配置时,FFmpeg 可以通过包的 AV_PKT_DATA_NEW_EXTRADATA 传递新的配置记录,供解码器更新参数。
显示模块根据实际输出帧适配:
- 从
AVFrame::width/height获取当前尺寸。 - 软件路径使用
sws_getCachedContext()更新转换上下文。 - 硬件路径根据 GPU 纹理尺寸和格式更新显示资源。
起播参数
BTV 使用以下设置控制流信息探测:
1 | // 设置流信息分析时长参数,单位为微秒。 |
起播耗时由 网络连接、流信息探测、关键帧到达和解码初始化共同决定。
播放过程中,项目还通过队列容量和时间戳同步控制积压:画面提前时等待,明显落后时丢弃过时画面,追赶播放时钟。
软解与硬解
软硬解共用:
1 | 流参数 → 打开解码器 → 送压缩包 → 取解码画面 |
主要区别是初始化配置和画面输出位置:
| 环节 | 软件解码 | D3D11 硬件解码 |
|---|---|---|
| 初始化 | 设置软件线程数和 FF_THREAD_FRAME |
设置硬件设备与格式选择回调 |
| 输出画面 | CPU 内存中的像素 | GPU 解码表面 |
| 帧处理 | 转换为 NV12,再上传显示 | 保留 AVFrame 和设备引用 |
| 显示 | 采样上传后的 YUV 纹理 | 直接采样或通过 GPU 内部 YUV 复制准备纹理 |
最终由 shader 在绘制时完成 YUV 到 RGB 的颜色转换。
面试回答模板
项目的 AVC 线路使用 HTTP-FLV。我通过 FFmpeg 打开输入、探测流信息,取得包含 SPS/PPS 配置记录的流参数,再复制到解码上下文并打开解码器。
解封装线程持续读取压缩包,分发给音视频队列和缓存、录制旁路;视频线程使用 send/receive 接口解码,再根据时间戳同步并发布画面。AVCC 和 Annex-B 的解析由 FFmpeg 完成,应用使用统一接口。
正常起播由解码器建立参考状态;视频解码回退等恢复场景会等待关键包。seek 时清理队列、刷新解码状态并重置时钟。起播探测使用较小的
probesize和analyzeduration,播放积压通过队列和同步调度控制。D3D11 硬解输出 GPU 表面,项目保留帧和设备引用,通过纹理采样或 GPU 内部 YUV 复制完成显示准备,最终由 shader 完成颜色转换。

