Sleepstars 的记录室

Sleepstars 的记录室

周报 #26W3 记录流媒体推流(Emby)文件播放提速

4
2026-01-19

1. 流媒体推流优化

1.1 我在做什么?

最近闲来无事,想要维护一下 Emby 服务器。当前的架构是

用户 >> 推流器 >> 网盘

但是我遇到了一个很大的问题,就是每次播放都需要花费大概 10s 的时间,但是拖动进度条的时候又不会出现相关问题。

为了解决这个问题,决定抓包试试。

1.2 抓包查找问题

首先播放了一部我最近下载下来的视频媒体文件,使用自己的推流工具(模拟了 emby 的推流行为),客户端为 Senplayer。

  1. 首先客户端会 range 一个 0- ,获取文件的基础信息。在这里可以看到总的 content-range 为 1070549000 bytes,也就是大约 1.07 GB 的大小;

  1. 接下来播放器会请求视频的尾段,可以看到其尝试性获取了最后的 1070549000 - 1070545120 = 3880 bytes = 3.8 KB 的数据;

  1. 从 6921 开始读取整个视频内容;

  1. 又读取 1070269002 开始的视频内容;

  1. 最后正式从 6921 开始下载完整的视频内容,并开始播放。

1.3 行为分析

目前最常见的两种视频封装格式分别为 mp4 和 mkv。在默认常规的编码情况下分别应该是这样的:

  • MP4

[文件头]  [================ 巨大的媒体数据 (mdat) ================]  [索引 (moov)]
   ↓                                                                    ↑
   0% (开始下载)                                                 100% (终于拿到索引,开始播放)
  • MKV

[EBML头] [轨道信息] [================== 视频簇 (Clusters) ==================] [索引 (Cues)]
   ↓                                                                              ↑
   0% (没找到索引,被迫发起新请求跳转到这里读取) -----------------------------------> 100%

在播放一个视频的时候,播放器需要知道一个视频的索引信息,也就是需要知道一个视频的总长度,以及在某一秒的时候,视频是在哪一帧(关键帧),以实现跳转功能。所以在正式开始播放之前,播放器的动作会是:

  1. 请求 range 0- 获取视频文件总大小;

  2. 对 mp4 获取一段文件头,对 mkv 也是类似的;

  3. 播放器找不到索引信息,猜测/找到尾部的索引位置,再请求获取索引信息建立视频进度条;

  4. 最后正式开始请求视频主体信息,开始播放。

这一路请求下来,大概需要 4 个 RTT。在本地播放的时候,硬盘检索和索引非常的快,所以完全没有任何影响。然而对于存储在网盘上,需要从网盘获取二进制数据再返回给用户的情景来说,这个问题就非常致命了。从网盘获取单个文件的 RTT 可能会达到 200~500ms 的 rtt,再加上用户到服务器的网络延时和 TCP 握手的延时,那么 4 个 rtt 就会消耗就会消耗 5s 以上的时间。

那么是否有什么解决方法吗?

1.4 解决方案

既然如此,你肯定会想到,是否可以把索引放到文件头部而不是尾部,就可以减少两个 RTT 了呢?比如下面这样

  • MP4

[文件头]  [索引 (moov)]  [================ 巨大的媒体数据 (mdat) ================]
   ↓            ↑
   0%        1% (拿到索引,立刻起播)
  • MKV

[EBML头] [轨道信息] [索引 (Cues)] [================ 视频簇 (Clusters) ================]
   ↓                    ↑
   0%                1% (拿到索引,流畅起播)

既然已有了思路,经过一番搜索,发现其实现在 ffmpeg 就已经完全可以完成所有的操作了。

1.4.1 MP4 处理方法

faststart 处理(FFmpeg文档)

FFmpeg 有一个简单的 '-faststart' 就可以直接处理 MP4 文件,参考命令如下:

ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4

1.4.2 MKV 处理方法

cues 处理(FFmpeg 文档)

与 MP4 的简单处理不一样,MKV 稍微复杂一些,而且对版本有要求。faststart 是 FFmpeg 在 2.4(2014年9月)版本引入的,包管理器中的包基本都包含。但是下面需要使用的 cues_to_front 是 FFmpeg 5.0(2022年1月)才加入。需要注意使用的 FFmpeg 的版本。

参考命令如下:

ffmpeg -i input.mkv -c copy -reserve_index_space 1M -cues_to_front 1 output.mkv

reserve_index_space 是预留给索引的空间,官方文件推荐的保守值是 1 小时 50KB。考虑到一个影片一般远超 1GB,直接预留 1 MB 的空间在空间前方是完全可以接受的保守值。

cues_to_front 则是无论索引多大,它保证一定能在文件头。就算有非常边缘的情况,索引超过了 1 MB 也不会被迫重新放到末尾。

那肯定就有人要问,为什么不只使用 -cues_to_front 1 呢?因为默认情况下 MKV 的 cues 就是会放在末尾。如果只使用单个参数,那么操作就会变成首先复制一遍,接着再把所有数据向后移动,会多浪费一遍时间(这里存疑,可能真的用一个就行)