RTSP 媒体协议流的录制方案及其覆盖策略详解(上)

2021-06-29 15:55:02 浏览数 (1)

【自我介绍】大家好,我是 Data-Mining,别名 liuzhen007,中国邦德,一个敲代码的邦德,前后就职于传统广电巨头和音视频互联网公司,具有丰富的音视频直播和点播相关经验,对WebRTC、FFmpeg和Electron有非常深入的了解。

前言

在安防和监控领域,RTSP 媒体协议流有很广泛的使用。本文将介绍一种针对 RTSP 媒体流的录制方案及其相应的覆盖策略。据我所知,声网的实时录制功能支持三种模式,分别是云端录制、本地服务端录制和页面录制,今天我们介绍的录制方案和声网的云端录制类似。

正文

本文将从录制视频格式的调研、录制方案的选择、异常状况的处理、覆盖策略的执行四个大方面进行介绍。

1. 录制视频格式调研

如果想要实现 RTSP 媒体流的录制功能,就需要考虑录制目标文件的格式,也就是把媒体流录制成哪种格式的视频文件。起初我们预设了三种方案,经过一系列调研后,最终选择了 m3u8。接下来,我们简单介绍一下这个选择过程。

1.1 为什么不用 mp4 格式

mp4 是点播视频中最为常见的视频格式,综合分析下来并不符合我们的使用场景。一般情况下,一个电影视频的最大时长也就两到三个小时左右,保存成一个 mp4 文件就够用了,但是在安防和监控场景下,一个摄像头对应的录制视频文件的长度可能是十几个小时,甚至是十几天。所以,对比下来,mp4 格式更适用于电影网站。

这就引出了 mp4 格式的一个缺点,如果录制存储为一个 mp4 格式,那文件体积可能会非常大。那么,存储的时候就会面临一系列问题,比如磁盘空间不足、大文件分片等状况的处理,特别是录制过程中数据流异常中断可能会导致已经录制的 mp4 文件不可用,这是其一。

我们知道 mp4 文件是由许多 Box 和 FullBox 组成的,可以参考上图的 Box 树形图,其中,FullBox 是 Box 的扩展,每个 Box 又包含 Header 和 Data 两部分,moov Box 记录了整个 mp4 文件的音视频媒体信息。而 moov Box 一般是在 mp4 文件写完时才在文件尾部添加。因此,又引出了另外一个缺点,如果 mp4 文件特别大,那么在播放的时候,播放器需要加载全部的视频文件到内存中,如果视频文件特别大,这几乎是不现实的。因此,我们在录制结束保存 mp4 的时候,需要把 moov Box 调整到文件头部来避免这个问题。

1.2 为什么不用 mpd 格式

mpd 格式类似于 m3u8 格式,但是它采用的是 XML 的组织形式。我们不选择它的原因也有两个,其一,mpd 格式在现有产品线上没有类似使用场景,我们使用更多的是 m3u8,换句话说就是技术储备不足。

其二,播放器方案的通用性上存在问题,如果使用 mpd 格式,那么我们的播放器方案需要调整,能够支持 mpd 格式媒体的播放,这样一来会给播放器带来一定的工作量和隐含的问题。

最后,给出一个 mpd 的文件示例,让大家对其有一个更加直观的了解。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version 97fc982-release-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT734S">
  <Period id="0">
    <AdaptationSet id="0" contentType="audio" lang="en">
      <Representation id="0" bandwidth="131596" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
        <BaseURL>tears_audio_eng.mp4</BaseURL>
        <SegmentBase indexRange="745-1664" timescale="44100">
          <Initialization range="0-744"/>
        </SegmentBase>
      </Representation>
    </AdaptationSet>
    <AdaptationSet id="1" contentType="video" maxWidth="1920" maxHeight="856" frameRate="12288/512" par="38:17">
      <Representation id="1" bandwidth="769255" codecs="avc1.42c01e" mimeType="video/mp4" sar="852:857" width="320" height="142">
        <BaseURL>tears_h264_baseline_240p_800.mp4</BaseURL>
        <SegmentBase indexRange="827-1602" timescale="12288">
          <Initialization range="0-826"/>
        </SegmentBase>
      </Representation>
      <Representation id="2" bandwidth="1774254" codecs="avc1.4d401f" mimeType="video/mp4" sar="2242:2249" width="854" height="380">
        <BaseURL>tears_h264_main_480p_2000.mp4</BaseURL>
        <SegmentBase indexRange="829-1604" timescale="12288">
          <Initialization range="0-828"/>
        </SegmentBase>
      </Representation>
      <Representation id="3" bandwidth="7203938" codecs="avc1.4d4028" mimeType="video/mp4" sar="855:857" width="1280" height="570">
        <BaseURL>tears_h264_main_720p_8000.mp4</BaseURL>
        <SegmentBase indexRange="830-1605" timescale="12288">
          <Initialization range="0-829"/>
        </SegmentBase>
      </Representation>
      <Representation id="4" bandwidth="18316946" codecs="avc1.64002a" mimeType="video/mp4" sar="856:857" width="1920" height="856">
        <BaseURL>tears_h264_high_1080p_20000.mp4</BaseURL>
        <SegmentBase indexRange="832-1607" timescale="12288">
          <Initialization range="0-831"/>
        </SegmentBase>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

通过上述文件,我们可以知道这个 mpd 文件包含了一路音频流,同时支持三种不同分辨率和码率的视频流。不同的媒体类型是用 AdaptationSet 标签表示的,内部还可以使用 Representation 标签标记不同分辨率和码率的媒体流。

1.3 为什么最终选择 m3u8 格式

选择 m3u8 的话,优势就会更加明显,除了规避上述方案的问题外,还有一些自身的优势,具体表现如下:

1)本身就是 ts 分片存储形式,不需要再单独考虑大文件的切片问题。

2)现有播放器方案支持 m3u8 格式,不需要再单独进行适配。

3)具有一定的技术储备,开发上手快,开发周期可控。

4)相应的覆盖策略执行起来会更加方便。

最后,给出一个 m3u8 的文件示例,让大家对其有一个更加直观的了解。

代码语言:javascript复制
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:17
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:11.933333,
index_0000.ts
#EXTINF:3.866667,
index_0001.ts
#EXTINF:7.333333,
index_0002.ts
#EXTINF:16.666667,
index_0003.ts
#EXTINF:4.133333,
index_0004.ts
#EXT-X-ENDLIST

通过上述文件,我们可以知道这个 m3u8 文件包含了 5 个 ts 分片,以及它们各自的时长信息。文件以 #EXTM3U 标签开始,并以 #EXT-X-ENDLIST 标签结束。这里有一点需要注意,如果是直播使用的 m3u8 文件,它是没有 #EXT-X-ENDLIST 标签的。

2. 录制方案选择

既然已经确定了目标文件的格式,那么我们就要考虑怎么实现了。目前有两个方案可以考虑,一个是 Golang 纯原生方案,另一个是利用 ffmpeg 实现,接下来分别介绍。

2.1 Go 原生

利用纯原生的 Golang 实现,其实,Golang 处理音视频数据还是有一定优势的,通过解封装 RTSP 媒体流,得到音频数据和视频数据,然后创建对应的解码器,得到对应的原始音频 PCM 数据和原始视频 YUV 数据,再分别编码成 AAC 的音频和 H264 的视频,最后保存成 m3u8 格式的录制文件。整个过程可以参考下图:

这种方案,编码的工作量会稍微大一些,同时有很多音视频数据处理的细节问题,负载度和难易程度上不如 ffmpeg 方案。

2.2 ffmpeg

利用 ffmpeg 工具库,通过启用 ffmpeg 进程来完成对应的 RTSP 流数据接收和 m3u8 文件录制保存工作,这样会更加简单,我们只需要管理好进程的创建、释放和异常处理工作。

(更多内容下篇更新,欢迎订阅!)

0 人点赞