ffmpeg - 媒体源扩展中的有趣行为
问题描述
我正在尝试使用 Media Source Extensions 构建一个相当标准的视频播放器;但是,我希望用户能够控制播放器何时移动到新的视频片段。例如,我们可能会看到以下行为:
- 视频播放器播放第一段
- 源缓冲区用完数据导致视频出现暂停
- 当用户准备好时,他们单击一个按钮,将第二段添加到源缓冲区
- 视频继续播放第二段
这很好用,除了当视频在第 2 步中出现暂停时,它不会在第 1 段的最后一帧停止。相反,它会在第一段结束前停止两帧。最后两帧不会被丢弃,它们只是在用户单击按钮以推进视频后播放。这对我的应用程序来说是一个问题,我正在尝试找出一种方法来确保第 1 段中的所有帧都在 step 2 结束之前播放。
我怀疑最后两帧在视频解码器缓冲区中被阻塞。特别是因为在将第一个片段添加到源缓冲区后在我的媒体源上调用 endOfStream() 会导致第一个片段一直播放,并且没有留下任何帧。
附加信息
- 我使用以下 ffmpeg 命令从一系列 PNG 创建了每个视频片段文件
ffmpeg -i %04d.png -movflags frag_keyframe+empty_moov+default_base_moof video_segment.mp4
- 也许这是一个线索?未正确处理流结束情况(最后一帧被丢弃)
- 另一个需要注意的有趣的事情是,如果视频只有 2 帧或更少,MSE 根本不会播放它。
- 我使用的浏览器是 Chrome。我的 MSE 播放器的代码取自 Google Developers 示例,但为了完整起见,我将在此处发布。此代码仅涵盖第 2 步,因为这就是问题所在。
<script>
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.64001f"');
sourceBuffer.mode = 'sequence';
// Fetch the video and add it to the Source Buffer
fetch('https://s3.amazonaws.com/bucket_name/video_file.mp4')
.then(response => response.arrayBuffer())
.then(data => sourceBuffer.appendBuffer(data));
}
解决方案
这很好用,除了当视频在第 2 步中出现暂停时,它不会在第 1 段的最后一帧停止。相反,它会在第一段结束前停止两帧。最后两帧没有被丢弃,它们只是在之后播放......
此行为取决于浏览器。让我们从规范报价开始:
当媒体元素需要更多数据时,用户代理应该足够早地将其从 HAVE_ENOUGH_DATA 转换为 HAVE_FUTURE_DATA 以便 Web 应用程序能够响应而不会导致播放中断。例如,当当前播放位置在缓冲数据结束前 500 毫秒时进行转换,使应用程序有大约 500 毫秒的时间在播放停止前追加更多数据。
您看到的行为是与 MSE 兼容的浏览器知道流尚未结束,但它也知道它正在用完数据。它通过更改其就绪状态来指示需要进一步的数据,但是它没有义务播放它已经拥有的每一帧。它根据当前播放的时钟时间与可用数据的结束进入缓冲状态。
即使上面的链接说...
例如,在视频中,这对应于用户代理具有来自当前帧的数据,但没有来自下一帧的数据
...实际的实现可能会对此有不同的解释,并且切换到HAVE_CURRENT_DATA
有点过早,即持有更多的视频帧,但知道它还没有结束流并且缺少更多的帧。这是一种你必须忍受的浏览器实现特性。