我用golang编写了HLS客户端

首先

这篇文章是Safe株式会社2022年12月12日Advent Calendar的一篇文章。

我们公司提供监控摄像头的云服务,并且负责视频传输的实施。目前我们有三种种类(HLS,WebRTC,YouTube Live)。这次我有机会在golang中实现其中一种,即HLS客户端,现在简单介绍一下。

这是已经实现的HLS客户端。

HLS 是什么意思?

HLS是HTTP Live Streaming的缩写,是一种用于在HTTP上进行流媒体传输的协议。其基本原理并不复杂,服务器会将视频(画面和音频)分割成一定的单元,客户端会下载这些单元并进行播放。为了能够知道应该下载多久的视频才够,需要知道视频文件的数量和播放时间等信息。因此,这些信息会被保存在称为播放列表的文本文件中,首先获取播放列表,然后再进行视频的下载。

undefined

赛菲的HLS

HLS有两种类型,即点播流媒体和直播流媒体,我们公司采用的是直播流媒体。
此外,HLS还具有自适应比特率流媒体功能,可以根据客户端的显示尺寸和网络带宽等条件切换下载视频的质量。
自适应比特率流媒体功能本身就很吸引人,但HLS最大的优势是支持的设备类型多。
此次我们实施的HLS客户端是直播流媒体和单一流媒体的HLS客户端。

动力 lì)

作为OSS的HLS客户端实现,例如有hls.js和ffmpeg等。如果有显示设备,像浏览器之类的,更常选择hls.js作为选项。但是,如果想获取我们公司相机的影像并进行自己的分析,hls.js不适用,因为它是为显示而设计的。那么该怎么办呢?我认为ffmpeg等可以作为选项。不过,由于ffmpeg具有相对较多的功能,仅为了HLS而采用ffmpeg可能有些过度,并且可能由于许可证问题而不容易采用。考虑到这种情况,因为单一的HLS直播相对较简单,所以我想尝试写一下。

为什么要选择Go语言的原因是什么?

在我们公司,后端的大部分部分,特别是API服务器,是用Python编写的。这是基于历史原因,但主要原因是我们拥有很多现有的资产,并且有很多熟悉Python的团队成员。

另一方面,处理视频部分通常需要更高的性能要求,因此在过去我们主要使用Java、C++等进行编写,而在最近我们开始采用Go语言。另外,我们也在API服务器的某些部分采用了Go语言。由于这样的背景,我们有更多机会接触到Go语言,同时我们的HLS流媒体服务器也是用Go语言编写的,因此我们考虑尝试使用Go语言来编写客户端。

实际的歌单 de

下一步,我们将检查我们公司从HLS服务器传输的播放列表,并对其中的主要内容进行确认。
有关详细信息,请参阅RFC8216。

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:3
#EXTINF:2.266667,
stream_0.ts

#EXT-X-MEDIA-SEQUENCE: 该播放列表文件中显示的第一个媒体片段的媒体序列号。

在這個例子中,0代表著第一個媒體片段的序號為0。如果獲取下一個播放清單,通常EXT-X-MEDIA-SEQUENCE會變成1。

#EXTINF: :表示媒体片段的持续时间。EXTINF标签的下一行是片段,并表示其时间。

这个例子表示了片段文件 stream_0.ts 的持续时间为2.266667秒。

#EXT-X-TARGETDURATION: 该播放列表中媒体片段的最大持续时间。

这个例子中只有一个媒体分段,时长为3秒。

詳盡的序列

HLS的序列图如下所示。
当客户端获取到服务器上的playlist.m3u8文件时,服务器开始生成分段文件(如stream_0.ts)。
当积累了一定数量的分段文件(此例中为3个)时,服务器会返回playlist.m3u8文件。
获取到playlist的客户端根据其内容获取3个分段文件。
之后会循环进行playlist的获取和分段文件的获取。

undefined

在实施过程中提出的疑问

由于在实施过程中对获取播放列表的时间有些犹豫,所以我们将讨论这个问题。

    • playlistの取得が完了したらすぐ

最も単純です.しかしplaylistに変更がない場合、ネットワーク帯域の圧迫やサーバーの負荷上昇が発生してしまいます。RFCにも頻繁なリロードはしてはならないとあります.

playlistに含まれる全てのセグメントをダウンロードした場合

全てのセグメントをダウンロードしていれば余計な負荷は発生しません.しかしこの方法だとplaylistの境界で動画が欠損する可能性があります.

playlist中セグメント時間の合計の少し前

最後に考えたのが、playlistに含まれるセグメントの合計時間の少し前に取得するというものです.そうすれば欠損はなくなると考えました.ただこの方法だと,取得したplaylistに既にダウンロードしたセグメントが含まれる場合があるため、それを除いたものにする必要があり、実装が面倒です.

经过仔细考虑并查阅了RFC,我确认到这一点都已经得到了清楚的描述(从一开始就应该进行确认)。

具体而言,我们将使用 EXT-X-TARGETDURATION。

初始请求将获取 EXT-X-TARGETDURATION ,当播放列表发生更改时,将间隔为 EXT-X-TARGETDURATION/2 来获取播放列表。EXT-X-TARGETDURATION 是播放列表中包含的片段的最大间隔,这样可以降低请求频率并实现简单的客户端实现。

目标代码如下所示。

		nextDuration := time.Second * time.Duration(newPlaylist.targetDuration)
		if prevPlaylist != nil {
			if newPlaylist == prevPlaylist {
				// If there is no change in the playlist, do not acquire the segment and wait only for targetDuration
				to = *time.NewTimer(nextDuration)
				continue
			} else {
				// If there is a change in the playlist, acquire the segment and wait for targetDuration/2
				nextDuration /= 2
			}
		}

RFC太棒了。我们要认真阅读。

最終的***
最终***

希望这个HLS客户端的实现介绍能对需要HLS客户端的场合有所帮助。虽然简单,但以上就是我在Go语言中实现HLS客户端的介绍到此为止了。

广告
将在 10 秒后关闭
bannerAds