HTTP Live Streaming 简介:Android 上的 HLS 等

已发表: 2022-03-11

视频流是现代互联网体验不可或缺的一部分。 它无处不在:在手机、台式电脑、电视甚至可穿戴设备上。 它需要在每种设备和网络类型上完美运行,无论是在慢速移动连接、WiFi、防火墙后面等等。Apple 的 HTTP Live Streaming (HLS) 正是针对这些挑战而创建的。

几乎所有现代设备都配备了足以播放视频的现代硬件,因此网络速度和可靠性成为最大的问题。 这是为什么? 直到几年前,存储和发布视频的规范方式是基于 UDP 的协议,如 RTP。 这在很多方面都被证明是有问题的,仅列出几个:

  1. 您需要一个服务器(守护程序)服务来流式传输内容。
  2. 大多数防火墙配置为仅允许标准端口和网络流量类型,例如 http、电子邮件等。
  3. 如果您的受众是全球性的,您需要一份在所有主要区域运行的流式守护程序服务的副本。

当然,您可能认为所有这些问题都很容易解决。 只需将视频文件(例如,mp4 文件)存储在您的 http 服务器上,然后使用您喜欢的 CDN 服务在世界任何地方为它们提供服务。

传统视频流的不足之处

由于几个原因,这远非最佳解决方案,效率就是其中之一。 如果您以全分辨率存储原始视频文件,农村地区或世界上连接性较差的地区的用户将很难享受这些文件。 他们的视频播放器将难以下载足够的数据以在运行时播放。

因此,您需要一个特殊版本的文件,以便下载的视频量与可以播放的量大致相同。 例如,如果视频分辨率和质量能够在 5 秒内下载另外 5 秒的视频,那就是最佳选择。 但是,如果只下载 3 秒的视频需要 5 秒,则播放器将停止并等待流的下一个块下载。

另一方面,进一步降低质量和分辨率只会降低更快连接的用户体验,因为您会不必要地节省带宽。 但是,还有第三种方法。

自适应比特率流

虽然您可以为不同的用户上传不同版本的视频,但您需要能够控制他们的播放器并计算最适合他们的连接和设备的流。 然后,玩家需要在它们之间进行切换(例如,当用户从 3G 切换到 WiFi 时)。 即便如此,如果客户端更改了网络类型怎么办? 然后播放器必须切换到不同的视频,但它必须不是从头开始播放,而是在视频中间的某个地方开始播放。 那么如何计算请求的字节范围呢?

如果视频播放器可以检测到网络类型和可用带宽的变化,然后在不同流(为不同速度准备的同一视频)之间透明地切换,直到找到最佳流,这将是一件很酷的事情。

这正是自适应比特率流解决的问题。

注意:本 HLS 教程不涉及加密、同步播放和 IMSC1。

什么是 HLS?

HTTP Live Streaming 是 Apple 于 2009 年推出的一种自适应比特率流协议。它使用 m3u8 文件来描述媒体流,并使用 HTTP 进行服务器和客户端之间的通信。 它是所有 iOS 设备的默认媒体流协议,但它可以在 Android 和 Web 浏览器上使用。

HTTP Live Streaming 封面图

HLS 流的基本构建块是:

  1. M3U8 播放列表
  2. 各种流媒体文件

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标签包含每个视频“块”的最长持续时间。 通常,此值约为 10 秒。
  • 文档的其余部分包含成对的行,例如:
 #EXTINF:10.344822, 00001.ts

这是一个视频“块”。 这个代表00001.ts块,正好是 10.344822 秒长。 当客户端视频播放器需要从所述视频中的某个点开始播放视频时,它可以通过将之前查看的块的持续时间相加来轻松计算出它需要请求哪个.ts文件。 第二行可以是本地文件名或该文件的 URL。

M3U8 文件及其.ts文件代表了 HLS 流的最简单形式——媒体播放列表。

请记住,并非所有浏览器默认都可以播放 HLS 流。

主播放列表或索引 M3U8 文件

前面的 M3U8 示例指向一系列.ts块。 它们是从原始视频文件创建的,该文件经过调整大小编码并分成块。

这意味着我们仍然存在引言中概述的问题 - 非常慢(或异常快)网络上的客户端呢? 或者,屏幕尺寸非常小的快速网络上的客户端? 如果无法在闪亮的新手机上完整显示文件,那么以最大分辨率流式传输文件是没有意义的。

HSL 中的 M3U8

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 ) 包含调整为640x360像素并为1296kbps比特率准备的视频的视频文件块。 请注意,报告的比特率必须同时考虑视频中的视频和音频流

视频播放器通常会从第一个流变开始播放(在前面的示例中,这是 640x360_1200.m3u8)。 因此,您必须特别注意确定哪个变体将是列表中的第一个。 其他变体的顺序并不重要。

如果第一个 .ts 文件下载时间过长(导致“缓冲”,即等待下一个块),视频播放器将切换到比特率较小的流。 而且,当然,如果它的加载速度足够快,则意味着它可以切换到质量更好的变体,但前提是它对显示器的分辨率有意义。

如果索引 M3U8 列表中的第一个流不是最佳流,则客户端将需要一到两个周期,直到它使用正确的变体稳定下来。

所以,现在我们有了三层 HLS:

  1. 包含指向变体的指针 (URL) 的索引 M3U8 文件(主播放列表)
  2. 适用于不同屏幕尺寸和网络速度的不同流的变体 M3U8 文件(媒体播放列表) 。 它们包含指向 .ts 文件的指针 (URL)。
  3. .ts文件(块)是包含部分视频的二进制文件。

您可以在此处观看示例索引 M3U8 文件(同样,这取决于您的浏览器/操作系统)。

有时,您提前知道客户端在慢速或快速网络上。 在这种情况下,您可以通过提供具有不同第一个变体的索引 M3U8 文件来帮助客户选择正确的变体。 有两种方法可以做到这一点。

  • 首先是为不同的网络类型准备多个索引文件,并提前准备好客户端以请求正确的索引文件。 客户端必须检查网络类型,然后请求例如http://.../index_wifi.m3u8http://.../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 中的音频。

HLS 视频

这意味着 30 秒的视频将被分成 3 个较小的.ts文件,每个文件大约 10 秒长。

请注意,最新版本的 HLS 也允许使用碎片化的 .mp4 文件。 由于这还是一个新事物,并且一些视频播放器还需要实现它,所以本文中的示例将使用.ts文件。

关键帧

必须在每个文件的开头使用关键帧对块进行编码。 每个视频都包含帧。 帧是图像,但视频格式不存储完整的图像,这会占用太多磁盘空间。 它们仅编码与前一帧的差异。 当您跳到视频的中间点时,播放器需要一个“起点”来应用所有这些差异以显示初始图像,然后开始播放视频。

这就是为什么.ts块必须在开始时有一个关键帧。 有时玩家需要从区块的中间开始。 玩家总是可以通过添加来自第一个关键帧的所有“差异”来计算当前图像。 但是,如果它从开始 9 秒开始,它需要计算 9 秒的“差异”。 为了使计算更快,最好每隔几秒创建一次关键帧(最佳 cca 3s)。

HLS 断点

在某些情况下,您希望连续播放多个视频剪辑。 一种方法是合并原始视频文件,然后使用该文件创建 HLS 流,但由于多种原因,这是有问题的。 如果您想在视频之前或之后展示广告怎么办? 也许您不想对所有用户都这样做,并且可能希望为不同的用户提供不同的广告。 而且,当然,您不希望提前准备带有不同广告的 HLS 文件。

为了解决这个问题,可以在 m3u8 播放列表中使用标签#EXT-X-DISCONTINUITY 。 这一行主要是告诉视频播放器提前准备好,从这一点开始, .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 文件使用无缓存标头提供服务,因为客户端将不断重新加载 M3U8 文件以等待新块播放。

VTT

HLS 流的另一个有趣功能是您可以在其中嵌入 Web 视频文本轨道 (VTT) 文件。 VTT 文件可用于各种用途。 例如,对于网络 HLS 播放器,您可以为视频的各个部分指定图像快照。 当用户将鼠标移到视频计时器区域(视频播放器下方)上时,播放器可以显示视频中该位置的快照。

VTT 文件的另一个明显用途是字幕。 HLS 流可以为多种语言指定多个字幕:

 #EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-,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"

然后, theprog_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 文件,Apple 最近还宣布 HLS 将支持 IMSC1,这是一种针对流媒体传输进行了优化的新字幕格式。 它最重要的优点是可以使用 CSS 设置样式。

HTTP 实时流媒体工具和潜在问题

Apple 引入了许多有用的 HSL 工具,在官方 HLS 指南中有更详细的描述。

  • 对于实时流,Apple 准备了一个名为mediastreamsegmenter的工具,用于从正在进行的视频流中动态创建片段文件。
  • 另一个重要的工具是mediastreamvalidator 。 它将检查您的 M3U8 播放列表、下载视频文件并报告各种问题。 例如,当报告的比特率与从 .ts 文件计算的不同时。
  • 当然,当你必须编码/解码/mux/demux/chunk/strip/merge/join/...视频/音频文件时,还有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);

结果如下:

Android 中的 HLS 流式传输示例

HLS 浏览器兼容性,未来发展

Apple 要求 iOS 上的视频流应用程序必须使用 HLS,如果视频超过 10 分钟或大于 5mb。 这本身就是 HLS 将继续存在的保证。 有人担心 HLS 和 MPEG-DASH 以及哪一个将成为 Web 浏览器领域的赢家。 HLS 并未在所有现代浏览器中实现(您可能会注意到,如果您单击前面的 m3u8 url 示例)。 例如,在 Android 上,在低于 4.0 的版本中,它根本无法工作。 从 4.1 到 4.4,它只能部分工作(例如,音频丢失,或视频丢失但音频有效)。

但最近这场“战斗”变得稍微简单了一些。 Apple 宣布新的 HLS 协议将允许分段的 mp4 文件 ( fMP4 )。 以前,如果您想要同时支持 HLS 和 MPEG-DASH,您必须对视频进行两次编码。 现在,您将能够重复使用相同的视频文件,并且仅重新打包元数据文件(用于 HLS 的.m3u8和用于 MPEG-DASH 的.mpd )。

最近的另一个公告是对高效视频编解码器 (HEVC) 的支持。 如果使用,必须打包成碎片化的 mp4 文件。 这可能意味着 HLS 的未来是fMP4

浏览器领域的当前情况是,只有<video>标签的某些浏览器实现会开箱即用地播放 HLS。 但也有提供 HLS 兼容性的开源和商业解决方案。 他们中的大多数通过 Flash 后备提供 HLS,但也有一些完全用 JavaScript 编写的实现。

包起来

本文特别关注 HTTP Live Streaming,但从概念上讲,它也可以理解为对自适应比特率流 (ABS) 工作原理的解释。 总之,我们可以说 HLS 是一种解决视频流中许多重要问题的技术:

  • 它简化了视频文件的存储
  • 内容分发网络
  • 客户端播放器处理不同的客户端带宽并在流之间切换
  • 本文未涉及的字幕、加密、同步播放和其他功能

无论您最终使用 HLS 还是 MPEG-DASH,这两种协议都应该提供相似的功能,并且通过在 HLS 中引入分段 mp4 (fMP4),您可以使用相同的视频文件。 这意味着在大多数情况下,您需要了解这两种协议的基础知识。 幸运的是,它们似乎朝着同一个方向发展,这应该使它们更容易掌握。