MPEG-DASH(基于HTTP的动态自适应流,Dynamic Adaptive Streaming over HTTP)技术可以使播放器在播放过程中根据网速实时调节视频清晰度,起到降低缓冲时间、提升画质的效果。有关 DASH 的详细介绍请看维基百科。
本文主要介绍将视频封装为 DASH 格式,并在网页上播放的方法。
要运行下文的大部分命令,您必须安装好 ffmpeg 和 x264,并且配置环境变量(Windows)等内容。或者,您也可以下载或编译出 ffmpeg 和 x264 的二进制文件,然后将下文命令中的 ffmpeg
和 x264
替换成完整路径。
在我的测试中,MP4Box 对帧率不同的视频支持状况不佳,会导致播放速度出错。
下面将介绍在 Ubuntu 18.04 中安装 ffmpeg 和 x264 的过程。本文编写时使用 ffmpeg 4.1.4 版本。x264 压制参数变化不大,但出现问题时建议尝试更新版本。同时,你也可以通过其他方式安装 ffmpeg 和 x264。
本文直接使用别人编译好的二进制。本文不对这些二进制文件的安全性负责。
运行命令
sudo add-apt-repository ppa:jonathonf/ffmpeg-4
sudo apt-get update
按提示操作。
sudo apt install -y ffmpeg x264
要实现画质切换,您需要将视频压制成不同清晰度。如果您喜欢,可以将音频也压制成不同的规格和码率。
注意点:按照本文的步骤,没有必要严格对齐 GOP。因为本文中会对不同清晰度的视频采用不同的帧率。
下面是压制命令。这条命令使用 ffmpeg 解码并改变视频帧率,然后通过管道将未压缩视频传给 x264 进行编码。
ffmpeg -i ${input} -an -r 60000/1001 -f yuv4mpegpipe -pix_fmt yuv420p - | x264 --crf ${crf} --demuxer y4m --preset veryslow -I ${i_frame} -r 4 -b 3 --me umh -i 1 --scenecut 60 -f 1:1 --qcomp 0.5 --psy-rd 0.3:0 --aq-mode 2 --aq-strength 0.8 --vf resize:3840,2160,,,, -o "${output_prefix}-2160p60.mp4" -
在上面的命令中:
${input}
表示源文件名-r 60000/1001
表示将视频帧率变为每秒 60000/1000 帧(约为 60,但是兼容性好于整数 60)${crf}
表示使用的 crf 值,您可以使用 22 左右的数值${i_frame}
表示最大关键帧间隔,单位是帧,不可过大,建议使用每秒帧率*2(舍入至最接近的整数即可)--vf resize:3840,2160,,,,
会将输出视频缩放至指定的分辨率-o "${output_prefix}-2160p60.mp4"
表示输出文件。您也可以改用任何您喜欢的方式进行编码,但是需要注意兼容性。同时,也不要使关键帧间隔过大。
修改上面命令中的参数,压制出不同清晰度和帧率的视频。
假定压制了 5 个不同清晰度的视频,分别是
fate.2160p60.mp4
fate.1080p60.mp4
fate.720p60.mp4
fate.450p.mp4
fate.360p.mp4
同时,提取源文件中的音频(假定为 aac 编码。如果不是 aac 编码,您需要自行转换)
ffmpeg -i ${input} -vn -c copy fate.a.m4a
该命令会输出名为 fate.a.m4a
的音频流。
然后我们就可以使用 ffmpeg 生成分片了。
首先建立 fate 文件夹,用于存放生成的分片。文件夹的名称无要求,但是您需要修改后面的命令来确保将文件输出到您建立的文件夹中。
然后执行以下命令:
ffmpeg \
-i fate.2160p60.mp4 \
-i fate.1080p60.mp4 \
-i fate.720p60.mp4 \
-i fate.450p.mp4 \
-i fate.360p.mp4 \
-i fate.a.m4a \
-c copy \
-map 0 -map 1 -map 2 -map 3 -map 4 -map 5 \
-f dash \
-adaptation_sets "id=0,streams=v id=1,streams=a" \
fate/manifest.mpd
接下来 ffmpeg 会输出大量信息。完成后,我们可以在 fate 文件夹中发现分好的分片。
打开 fate/manifest.mpd 文件(或者你指定的其他路径)。可以看到文件开头如下。
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:mpeg:dash:schema:mpd:2011"
xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
profiles="urn:mpeg:dash:profile:isoff-live:2011"
type="static"
mediaPresentationDuration="PT1M34.0S"
minBufferTime="PT10.0S">
我们关注的是这一部分。
minBufferTime="PT10.0S"
这个属性的意思是,视频必须至少缓冲 10 秒才能开始播放。这里的数字可能因压制参数不同而变化。但是,缓冲 10 秒太长了,不但没有必要,还会严重拖慢载入速度。实际上,一般载入一点就可以开始播放了。因此,我们可以把时间改为 1.0 秒。
minBufferTime="PT1.0S"
有很多 js 库可以播放 DASH 视频。本文使用谷歌 shaka 播放器播放 DASH 视频。
dash.js 似乎也是使用比较广泛的 DASH 播放库。但是我在使用 dash.js
的过程中经常卡死,调试工具显示播放器反复请求同一分片。不确定是否是 GOP 未严格对齐的问题。
为了避免这个问题,干脆换用其他库。我在使用 shaka 的过程中没有遇到疑难问题。
首先创建 myapp.js 文件。内容如下。
// myapp.js
function initApp() {
// Install built-in polyfills to patch browser incompatibilities.
shaka.polyfill.installAll();
// Check to see if the browser supports the basic APIs Shaka needs.
if (shaka.Player.isBrowserSupported()) {
// Everything looks good!
initPlayer();
} else {
// This browser does not have the minimum set of APIs we need.
console.error('Browser not supported!');
}
}
function initPlayer() {
// Create a Player instance.
var video = document.getElementById('video');
var src = video.src;
var player = new shaka.Player(video);
player.configure({
streaming: {
bufferingGoal: 45,
bufferBehind: 5,
retryParameters: {
timeout: 0, // timeout in ms, after which we abort; 0 means never
maxAttempts: 200, // the maximum number of requests before we fail
baseDelay: 100, // the base delay in ms between retries
// backoffFactor: 2, // the multiplicative backoff factor between retries
// fuzzFactor: 0.5, // the fuzz factor to apply to each retry delay
}
},
abr: {
defaultBandwidthEstimate: 500, // bits per second.
switchInterval: 1
}
});
// Attach player to the window to make it easy to access in the JS console.
window.player = player;
// Listen for error events.
player.addEventListener('error', onErrorEvent);
// Try to load a manifest.
// This is an asynchronous process.
player.load(src).then(function () {
// This runs if the asynchronous load is successful.
console.log('The video has now been loaded!');
}).catch(onError); // onError is executed if the asynchronous load fails.
}
function onErrorEvent(event) {
// Extract the shaka.util.Error object from the event.
onError(event.detail);
}
function onError(error) {
// Log the error.
console.error('Error code', error.code, 'object', error);
}
document.addEventListener('DOMContentLoaded', initApp);
然后创建一个 html 文件。您需要根据您的路径修改 src
属性。
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/2.4.7/shaka-player.compiled.js">script>
<script src="myapp.js">script>
head>
<body>
<video id="video" src="fate/manifest.mpd" width="500" controls>video>
body>
将 js 文件、html 文件和 fate 文件夹一起上传到服务器,放到同一个目录。如果您网速较快,并且到您的服务器网速较好,应该可以看到视频由模糊变清晰,并且在网络波动时自动调整清晰度。若要修改默认清晰度,您可以修改 myapp.js 文件中的 defaultBandwidthEstimate
字段。 shaka 播放器会根据该字段设置的值选择默认清晰度。