本文概述
视频流是现代互联网体验不可或缺的一部分。它无处不在:在手机, 台式计算机, 电视甚至可穿戴设备上。它需要在各种设备和网络类型上都可以正常工作, 无论是在缓慢的移动连接, WiFi, 防火墙后面还是其他情况下。Apple的HTTP Live Streaming(HLS)正是在这些挑战的基础上创建的。
几乎所有现代设备都具有足够快的硬件来播放视频, 因此网络速度和可靠性成为最大的问题。这是为什么?直到几年前, 存储和发布视频的规范方法还是基于RTP的基于UDP的协议。事实证明, 这在许多方面都存在问题, 仅举几例:
- 你需要服务器(守护程序)服务以流式传输内容。
- 大多数防火墙配置为仅允许标准端口和网络流量类型, 例如http, email等。
- 如果你的受众是全球性的, 则需要在所有主要地区运行的流式守护程序服务的副本。
当然, 你可能会认为所有这些问题都很容易解决。只需将视频文件(例如mp4文件)存储在http服务器上, 然后使用你喜欢的CDN服务在世界上任何地方提供它们。
传统视频流不足之处
由于一些原因, 这远非最佳解决方案, 效率是其中之一。如果你以原始分辨率存储原始视频文件, 则农村地区或世界各地连接不畅的用户将很难享受它们。他们的视频播放器将难以下载足够的数据以在运行时播放。
因此, 你需要该文件的特殊版本, 以便下载的视频量与可以播放的视频量大致相同。例如, 如果视频的分辨率和质量足以在五秒钟之内下载另外五秒钟的视频, 那是最佳选择。但是, 如果要花五秒钟下载仅三秒钟的视频, 播放器将停止并等待流的下一部分下载。
另一方面, 进一步降低质量和分辨率只会降低更快连接时的用户体验, 因为这会不必要地节省带宽。但是, 还有第三种方式。
自适应比特率流
尽管你可以为不同的用户上传不同版本的视频, 但是你需要能够控制他们的播放器, 并计算出最适合他们的连接和设备的流。然后, 播放器需要在它们之间切换(例如, 当用户从3G切换到WiFi时)。即使这样, 如果客户端更改网络类型怎么办?然后, 播放器必须切换到其他视频, 但它必须不是从头开始播放, 而是从视频中间开始播放。那么, 如何计算要请求的字节范围?
如果视频播放器可以检测到网络类型和可用带宽的变化, 然后在不同的流(同一视频以不同的速度准备)之间透明切换, 直到找到最佳视频流, 那将是一件很酷的事情。
这正是自适应比特率流媒体所能解决的。
注意:本HLS教程将不涉及加密, 同步回放和IMSC1。
什么是HLS?
HTTP Live Streaming是Apple在2009年推出的一种自适应比特率流协议。它使用m3u8文件描述媒体流, 并且使用HTTP进行服务器和客户端之间的通信。它是所有iOS设备的默认媒体流协议, 但可以在Android和Web浏览器上使用。
HLS流的基本构建块是:
- M3U8播放列表
- 各种流的媒体文件
M3U8播放列表
首先, 回答一个基本问题:什么是M3U8文件?
M3U(或M3U8)是一种纯文本文件格式, 最初是为了组织MP3文件的集合而创建的。该格式针对HLS进行了扩展, 用于定义媒体流。在HLS中, 有两种m3u8文件:
- 媒体播放列表:包含流式传输所需文件的网址(即要播放的原始视频的一部分)。
- 主播放列表:包含指向媒体播放列表的URL, 这些URL又包含为不同带宽准备的同一视频的变体。
所谓的M3U8实时流URL只不过是指向M3U8文件的URL, 例如:https://s3-us-west-2.amazonaws.com/hls-playground/hls.m3u8。
HLS流的示例M3U8文件
M3U8文件包含URL列表或带有一些其他元数据的本地文件路径。元数据行以#开头。
此示例说明了用于简单HLS流的M3U8文件的外观:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:11
#EXTINF:5.215111, 00000.ts
#EXTINF:10.344822, 00001.ts
#EXTINF:10.344822, 00002.ts
#EXTINF:9.310344, 00003.ts
#EXTINF:10.344822, 00004.ts
...
#EXT-X-ENDLIST
- 前四行是此M3U8播放列表的全局(标题)元数据。
- EXT-X-VERSION是M3U8格式的版本(如果要使用EXTINF条目, 则必须至少为3)。
- EXT-X-TARGETDURATION标签包含每个视频”块”的最大持续时间。通常, 该值约为10s。
- 文档的其余部分包含多行, 例如:
#EXTINF:10.344822, 00001.ts
这是一个视频”大块”。这个代表00001.ts块, 恰好是10.344822秒。当客户端视频播放器需要从该视频中的某个点开始播放视频时, 它可以通过累加以前查看的块的持续时间来轻松地计算需要请求哪个.ts文件。第二行可以是本地文件名或该文件的URL。
M3U8文件及其.ts文件代表HLS流的最简单形式-媒体播放列表。你可以在此处打开一个简单的示例。
请记住, 并非默认情况下并非每个浏览器都可以播放HLS流。
主播放列表或索引M3U8文件
前面的M3U8示例指向一系列.ts块。它们是从原始视频文件创建的, 原始视频文件已调整大小并被编码成块。
这意味着我们仍然在引言中概述了问题–在非常慢(或异常快)的网络上的客户端呢?还是屏幕很小的快速网络上的客户端?如果无法以闪亮的新手机完全显示文件, 则无法以最大分辨率流式传输文件。
HLS通过引入M3U8的另一个”层”来解决此问题。该M3U8文件将不包含指向.ts文件的指针, 但具有指向其他M3U8文件的指针, 这些文件又包含针对特定比特率和分辨率预先准备的视频文件。
这是此类M3U8文件的示例:
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1296, RESOLUTION=640x360
https://.../640x360_1200.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=264, RESOLUTION=416x234
https://.../416x234_200.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=464, RESOLUTION=480x270
https://.../480x270_400.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1628, RESOLUTION=960x540
https://.../960x540_1500.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2628, RESOLUTION=1280x720
https://.../1280x720_2500.m3u8
视频播放器将选择多行, 例如:
#EXT-X-STREAM-INF:BANDWIDTH=1296, RESOLUTION=640x360
https://.../640x360_1200.m3u8
这些被称为为不同网络速度和屏幕分辨率准备的同一视频的变体。这个特定的M3U8文件(640x360_1200.m3u8)包含视频的视频文件块, 这些视频文件块的大小调整为640×360像素, 并准备好以1296kbps的比特率进行播放。请注意, 报告的比特率必须同时考虑视频中的视频和音频流。
视频播放器通常会从第一个流变体开始播放(在前面的示例中为640x360_1200.m3u8)。因此, 你必须格外小心, 以决定哪个变体将是列表中的第一个。其他变体的顺序并不重要。
如果第一个.ts文件下载时间过长(导致”缓冲”, 即等待下一个块), 则视频播放器将切换到比特率较小的流。而且, 当然, 如果加载速度足够快, 则意味着它可以切换到质量更好的版本, 但前提是它对显示器的分辨率有意义。
如果索引M3U8列表中的第一个流不是最佳流, 则客户端将需要一个或两个周期, 直到它找到正确的变体。
因此, 现在我们具有三层HLS:
- 索引M3U8文件(主播放列表), 包含指向变体的指针(URL)。
- 针对不同流的M3U8文件(媒体播放列表)的变体, 适用于不同的屏幕尺寸和网络速度。它们包含指向.ts文件的指针(URL)。
- .ts文件(块)是带有视频部分的二进制文件。
你可以在此处观看示例索引M3U8文件(同样, 它取决于你的浏览器/ OS)。
有时, 你事先知道客户端在慢速或快速网络上。在这种情况下, 你可以通过为索引M3U8文件提供不同的第一个变量来帮助客户选择正确的变量。有两种方法可以做到这一点。
- 首先是为不同的网络类型准备多个索引文件, 并事先准备客户端以请求正确的索引文件。客户端必须检查网络类型, 然后请求例如http://…/index_wifi.m3u8或http://…/index_mobile.m3u8。
- 你还可以确保客户端将网络类型作为http请求的一部分发送(例如, 如果连接到wifi或移动2G / 3G /…), 然后为每个请求动态准备索引M3U8文件。只有索引M3U8文件需要动态版本, 单个流(各种M3U8文件)仍可以存储为静态文件。
为HLS准备视频文件
Apple的HTTP Live Streaming有两个重要的构建块。一种是视频文件的存储方式(稍后将通过HTTP提供), 另一种是M3U8索引文件, 该文件告诉播放器(流客户端应用程序)在哪里获取哪个视频文件。
让我们从视频文件开始。 HLS协议期望将视频文件存储在相同长度的较小块中, 每个块通常为10秒。最初, 这些文件必须存储在MPEG-2 TS文件(.ts)中, 并以H.264格式编码, 并带有MP3, HE-AAC或AC-3音频。
这意味着30秒的视频将被分成3个较小的.ts文件, 每个文件长度约为10s。
请注意, 最新版本的HLS也允许使用片段化的.mp4文件。由于这仍然是新事物, 并且某些视频播放器仍需要实现它, 因此本文中的示例将使用.ts文件。
关键帧
在每个文件的开头, 必须使用关键帧对块进行编码。每个视频包含帧。帧是图像, 但是视频格式不能存储完整的图像, 这会占用过多的磁盘空间。它们仅编码与前一帧的差异。当你跳到视频的中间点时, 播放器需要一个”起点”, 从那里开始应用所有这些差异以显示初始图像, 然后开始播放视频。
因此, .ts块必须在开始时具有关键帧。有时玩家需要从大块的中间开始。播放器始终可以通过添加来自第一个关键帧的所有”差异”来计算当前图像。但是, 如果从起点开始9秒钟, 则需要计算9秒钟的”差异”。为了使计算速度更快, 最好每隔几秒钟创建一次关键帧(最佳cca 3s)。
HLS断点
在某些情况下, 你需要连续播放多个视频片段。一种方法是合并原始视频文件, 然后使用该文件创建HLS流, 但是由于多种原因, 这是有问题的。如果你想在视频之前或之后展示广告怎么办?也许你不想为所有用户都这样做, 也可能希望为不同的用户提供不同的广告。当然, 你也不想预先准备带有其他广告的HLS文件。
为了解决该问题, 有一个标签#EXT-X-DISCONTINUITY可以在m3u8播放列表中使用。此行基本上告诉视频播放器预先准备一个事实, 即从此时开始, 可以使用不同的配置创建.ts文件(例如, 分辨率可能会更改)。玩家将需要重新计算所有内容, 并可能切换到另一个变体, 并且需要准备好此类”不连续”点。
使用HLS进行直播
基本上有两种”视频流”。一种是视频点播(VOD), 用于预先录制的视频, 并在用户决定时流式传输给用户。还有直播。尽管HLS是HTTP Live Streaming的缩写, 但到目前为止, 所有解释都围绕着VOD, 但是也存在一种使用HLS进行实时流传输的方法。
M3U8文件中有一些更改。首先, 变体M3U8文件中必须存在#EXT-X-MEDIA-SEQUENCE:1标签。然后, M3U8文件不得以#EXT-X-ENDLIST结尾(否则必须始终放置在末尾)。
记录流时, 你将不断拥有新的.ts文件。你需要将它们添加到M3U8播放列表中, 并且每次添加新的列表时, #EXT-X-MEDIA-SEQUENCE:<counter>中的计数器都必须增加1。
视频播放器将检查计数器。如果从上次更改, 它将知道是否有新的块要下载和播放。确保M3U8文件带有no-cache标头, 因为客户端将继续重新加载M3U8文件, 以等待新的块播放。
山地自行车
HLS流的另一个有趣功能是你可以在其中嵌入Web视频文本跟踪(VTT)文件。 VTT文件可用于多种用途。例如, 对于Web HLS播放器, 你可以为视频的各个部分指定图像快照。当用户将鼠标移到视频计时器区域(位于视频播放器下方)上方时, 播放器可以显示视频中该位置的快照。
VTT文件的另一个明显用途是字幕。 HLS流可以为多种语言指定多个字幕:
#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="subs", NAME="English", DEFAULT=YES, AUTOSELECT=YES, FORCED=NO, LANGUAGE="en", CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound", URI="subtitles/eng/prog_index.m3u8"
然后, prog_index.m3u8看起来像:
#EXTM3U
#EXT-X-TARGETDURATION:30
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:30, 0000.webvtt
#EXTINF:30, 0001.webvtt
...
实际的VTT(例如0000.webvtt):
WEBVTT
X-TIMESTAMP-MAP=MPEGTS:900000, LOCAL:00:00:00.000
00:00:01.000 --> 00:00:03.000
Subtitle -Unforced- (00:00:01.000)
00:00:03.000 --> 00:00:05.000
<i>...text here... -Unforced- (00:00:03.000)</i>
<i>...text here...</i>
除了VTT文件外, 苹果公司最近宣布HLS将支持IMSC1, 后者是一种针对流传输而优化的新字幕格式。它最重要的优点是可以使用CSS设置样式。
HTTP实时流工具和潜在问题
Apple引入了许多有用的HSL工具, 在正式的HLS指南中对其进行了详细说明。
- 对于实时流, Apple准备了一个名为mediastreamsegmenter的工具, 可以从正在进行的视频流中即时创建片段文件。
- 另一个重要的工具是mediastreamvalidator。它将检查你的M3U8播放列表, 下载视频文件并报告各种问题。例如, 当报告的比特率与从.ts文件计算出的比特率不同时。
- 当然, 当你必须编码/解码/多路复用器/多路分配器/块/条/合并/合并/…视频/音频文件时, 有ffmpeg。准备针对特定用例编译自己的ffmpeg自定义版本。
视频中最常见的问题之一是音频同步。如果你发现某些HLS流中的音频与视频不同步(例如, 演员张开嘴, 但你注意到声音早或晚几毫秒), 则可能是原始视频文件被拍摄了使用可变帧率。确保将其转换为恒定比特率。
如果可能的话, 最好将你的软件设置为以恒定的帧率录制视频。
HTTP直播流示例
我准备了一个HLS Android应用程序, 该应用程序使用Google的ExoPlayer播放器流式传输预定义的HLS。它将在下面显示视频和HLS”事件”列表。这些事件包括:下载的每个.ts文件, 或者播放器每次决定切换到更高或更低的比特率流时。
让我们看一下查看器初始化的主要部分。第一步, 我们将检索设备的当前连接类型, 并使用该信息来确定要检索的m3u8文件。
String m3u8File = "hls.m3u8";
ConnectivityManager connectivity = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = connectivity.getActiveNetworkInfo();
if (activeNetwork != null && activeNetwork.isConnectedOrConnecting()) {
int type = activeNetwork.getType();
int subType = activeNetwork.getSubtype();
if (type == ConnectivityManager.TYPE_MOBILE && subType == TelephonyManager.NETWORK_TYPE_GPRS) {
m3u8File = "hls_gprs.m3u8";
}
}
String m3u8URL = "https://s3-us-west-2.amazonaws.com/hls-playground/" + m3u8File;
请注意, 这并非绝对必要。在几块之后, HLS播放器将始终调整为正确的HLS变体, 但这意味着在最初的5-20秒内, 用户可能不会观看理想的流变体。
请记住, m3u8文件中的第一个变体是查看器开始的变体。由于我们在客户端, 因此我们可以检测到连接类型, 因此我们至少可以通过请求为该连接类型预先准备的m3u8文件, 来避免初始玩家在变体之间进行切换。
在下一步中, 我们初始化并启动我们的HLS播放器:
Handler mainHandler = new Handler();
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder()
.setEventListener(mainHandler, bandwidthMeterEventListener)
.build();
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
LoadControl loadControl = new DefaultLoadControl();
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, loadControl);
然后, 我们准备播放器并为该网络连接类型提供正确的m3u8:
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "example-hls-app"), bandwidthMeter);
HlsMediaSource videoSource = new HlsMediaSource(Uri.parse(m3u8URL), dataSourceFactory, 5, mainHandler, eventListener);
player.prepare(videoSource);
结果如下:
HLS浏览器兼容性, 未来发展
Apple要求iOS上的视频流应用程序必须在视频长度超过10分钟或大于5mb时使用HLS。这本身就保证了HLS将继续存在。人们对HLS和MPEG-DASH感到有些担忧, 其中哪一种将成为网络浏览器领域的赢家。并非在所有现代浏览器中都实现了HLS(你可能会注意到, 如果你单击前面的m3u8网址示例)。例如, 在Android上, 低于4.0的版本根本无法使用。从4.1到4.4, 它仅部分起作用(例如, 音频丢失, 或者视频丢失, 但是音频有效)。
但是最近, 这种”战斗”变得更加简单了。苹果公司宣布, 新的HLS协议将允许分段的mp4文件(fMP4)。以前, 如果要同时支持HLS和MPEG-DASH, 则必须对视频进行两次编码。现在, 你将能够重用相同的视频文件, 并仅重新打包元数据文件(对于HLS是.m3u8, 对于MPEG-DASH是.mpd)。
最近的另一个公告是支持高效视频编解码器(HEVC)。如果使用, 则必须打包在分段的mp4文件中。这可能意味着HLS的未来是fMP4。
浏览器领域的当前情况是, 只有<video>标签的某些浏览器实现会开箱即用地播放HLS。但是, 有提供HLS兼容性的开源和商业解决方案。它们中的大多数通过具有Flash后备功能来提供HLS, 但是有一些完全用JavaScript编写的实现。
本文总结
本文专门讨论HTTP实时流, 但从概念上讲, 它也可以作为对自适应比特率流(ABS)工作原理的解释。总之, 我们可以说HLS是一项解决视频流中众多重要问题的技术:
- 它简化了视频文件的存储
- CDN
- 客户端播放器处理不同的客户端带宽并在流之间切换
- 字幕, 加密, 同步播放和本文未涵盖的其他功能
无论你最终使用HLS还是MPEG-DASH, 这两种协议都应提供相似的功能, 并且由于在HLS中引入了分段mp4(fMP4), 因此你可以使用相同的视频文件。这意味着在大多数情况下, 你需要了解两种协议的基础。幸运的是, 它们似乎朝着同一方向移动, 这应该使它们更易于掌握。