蓝将云手机实现探究-野生前端程序员的音视频入门

  • 最早,在一家做安防类的公司,使用海康威视等摄像头 提供的 视频流【 rtsp, rtmp 协议的 h264 视频流 】 在 浏览器客户端播放。

    • 在当时,只能使用的几种方式:
      1. 直接在借助 第三方浏览器插件 在浏览器播放。VLC 等第三方浏览器插件 提供的api 【 缺点: 需要 客户提前安装 插件,只有vlc 插件可以播放 rtsp的视频流 ,而已只在ie 已经过时 】
      2. 服务端转码 成为 web 直接可以播放的 hls(m3u8) [ 缺点 :需要服务器和更多带宽 成本较高,而且 实时性较差 ]
  • 参与云手机的研发【 web网页上 控制远端安卓手机 (触控层 + 视频层),安卓端 发送视频流,web 端进行解码播放 】

    1. 早期 解码方案 使用 第三方库 ws-avc-playerjs【 基于Broadway/Player解码 ( Webassembly + FFmpeg 视频解码 + canvas ) 】ws-avc-playerjs 虽然 不支持音频,没有使用流协议进行压缩传输,播放及其容易卡顿,但是其提供的 解决思路,确实值得深入学习。
    2. 借助 webrtc 解决方案,绕开 自己直接进行解码,音视频同步等一些列繁杂工作

蓝将云手机是什么

通过 web网页 控制远端安卓手机,实现广告中 软件、游戏的体验功能;游戏工作室 挂机代练等等使用场景。
蓝将网址

如何实现的

前端分为 视频层 和 触控层

  • 视频层 接收 远端安卓手机 视频流进行播放
  • 触控层 点击后通过 websocket 给安卓端 发送 事件消息

本文 仅以 介绍视频播放 方向内容

实现都经历了哪些过程

最早方案:使用 第三方库 ws-avc-playerjs【 基于Broadway/Player解码 ( Webassembly + FFmpeg 视频解码 + canvas ) 】

面临的问题:

  • 没有 音频 【 需求硬性要求 】
  • 播放时常 存在 延迟卡顿、累计延迟【 手机等内存设备更为明显 】

要解决上面问题,还得从头捋,了解 音视频基本知识 ,如何和完成视频播放。

音频

声音本质就是能量波,为了将模拟信号数字化 分别要对模拟信号进行 采样、量化和编码。

  1. 采样
  • 采样就是 在时间轴上对信号进行数字化。
  • 根据奈奎斯特定理(也称为采样定理),按比声音最高频率高 2 倍以上的频率对声音进行采样(也称为 AD 转换),
  • 对于高质量的音频信号,其频率范围(人耳 能够听到的频率范围)是 20Hz~20kHz,所以采样频率一般为 44.1kHz,这样就可以保证采样声音达到 20kHz 也能被数字化,从而使得 经过数字化处理之后,人耳听到的声音质量不会被降低。
  • 而所谓的 44.1kHz 就是代表 1 秒会采样 44100 次。
  1. 量化
    量化是指在幅度轴上对信号进行数字化,比如用 16 比特(16 个二进制位) 的二进制信号来表示声音的一个采样,而 16 比特(一个 short)所表示的 范围是[-32768,32767],共有 65536 个可能取值,因此最终模拟的音频 信号在幅度上也分为了 65536 层

  2. 编码
    所谓编码,就是按照一定的格式记录采样和量化后的数字数据,比如顺序存储或压缩存储,等等。

音频编码

上文中,提到了 CD 音质的数据采样格式,曾计算出每分钟需要的存 储空间约为 10.1MB,如果仅仅是将其存放在存储设备(光盘、硬盘) 中,可能是可以接受的,但是若要在网络中实时在线传播的话,那么这 个数据量可能就太大了,所以必须对其进行压缩编码。

压缩编码

  • 压缩编码的基本指标之一就是压缩比,压缩比通常小于 1(否则就没有必要去做压缩, 因为压缩就是要减小数据容量)
  • 压缩编码的原理实际上是压缩掉冗余信号,冗余信号是指不能被人 耳感知到的信号,包含人耳听觉范围之外的音频信号以及被掩蔽掉的音频信号等。

压缩算法:

  • 无损压缩:无损压缩是指解压后的数据可以完全复原。
  • 有损压缩:指解压后的数据不能完全复原,会丢失一部分信息;
    • 用得较多;
    • 压缩比越小,丢失的信息就越多,信号还原后的失真就会 越大;
    • 根据不同的应用场景(包括存储设备、传输网络环境、播放设备 等),可以选用不同的压缩编码算法,如 PCM、WAV、AAC、MP3、 Ogg 等

PCM

PCM 是软件层接收到最原始的音频格式数据。浏览器支持 audio api 直接播放 PCM
PCM(Pulse Code Modulation,脉冲编码调制) 音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准数字音频数据。

通常所说的音频的裸数据格式就是 PCM数据。描述一段 PCM 数据一 般需要以下几个概念: 量化格式(sampleFormat)、采样率 (sampleRate)、声道数(channel)。
以 CD 的音质为例:

  • 量化格式为 16-bit(2 字节),采样率(Sample Rate)为 44100,声道数为 2,这些信息就描述了 CD 的音质。
  • 而对于声音格式,还有一个概念用来 描述它的大小,称为数据比特率(即 1 秒时间内的比特数目,用于衡量 音频数据 单位时间内的容量大小)

对于 CD 音质的数据,比特率为:44100 * 16 * 2 = 1378.125kbps

  • 1 分钟里,这类 CD 音质的数据需要占据多大的存储需要 1378.125 * 60 / 8(8位是1字节得到KB) / 1024(KB换算MB) = 10.09MB

  • 如果 量化格式(sampleFormat) 更加精确【 比如用 4 字节(32-bit)来描述一个采样】,或者 采样率(sampleRate) 更加密集(比如 48kHz 的采样率),那么所占的 存储空间就会更大,同时能够描述的声音细节就会越精确。

    存储的这段二进制数据即表示将模拟信号转换为数字信号了,以后就可以对这段二进制数据进行存储、播放、复制,或者进行其他任何操作。

图像

视频是由一幅幅图像组成的,所以要学习视频还得从图像说起

  • RGB 表示方式:

    • 浮点表示:取值范围为 0.0 ~ 1.0,比如,在 OpenGL ES 中对每一个子像素点的表示使用的就是这种表达方式。
    • 整数表示:取值范围为0 ~ 255或者00 ~ FF,8 个比特表示一个子像素,32 个比特(除了 RGB 三色外,还有 alpha 透明度;也就是 4*8)表示一个像素,这就是类似于某些平台上表示图像格式的 RGBA_8888 数据格式。
      对于一幅图像,一般使用整数表示方法来进行描述, 比如计算一张 1280×720 的 RGBA_8888 图像的大小,可采用如下方式:1280 * 720 * 4 = 3.516MB 这也是位图(bitmap)在内存中所占用的大小,所以每一张图像的裸数据都是很大的。

    对于图像的裸数据来讲,直接在网络上进行传输也是不太可能的,所以就有了图像的压缩格式, 比如 JPEG 压缩:JPEG 是静态图像压缩标准,由 ISO 制定。

    JPEG 图像压缩算法在提供良好的压缩性能的同时,具有较好的重建质量。 这种算法被广泛应用于图像处理领域,当然其也是一种有损压缩。 在很多网站如淘宝上使用的都是这种压缩之后的图片,但是,这种压缩不能直接应用于视频压缩,因为对于视频来讲,还有一个时域上的因素需要考虑,也就是说,不仅仅要考虑帧内编码,还要考虑帧间编码。视频采用的是更成熟的算法。

  • YUV 表示方式【 主流 】

    • YUV 也是一种色彩编码方法,主要用于电视系统以及模拟视频领域。它将亮度信息(Y)与色彩信息(UV)分离,即使没有 UV 信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。
    • 与 RGB 视频信号传输相比,它最大的优点在于只需要占用极少的频宽(RGB 要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance 或 Luma),也称灰阶值;而“U”和“V”表示的则是色度(Chrominance 或 Chroma),它们的作用是描述影像的色彩及饱和度,用于指定像素的颜色。
    • “亮度”是透过 RGB 输入信号来建立的,方法是将 RGB 信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面——色调与饱和度,分别用 Cr 和 Cb 来表示。其中,Cr 反映了 RGB 输入信号红色部分与 RGB 信号亮度值之间的差异,而 Cb 反映的则是 RGB 输入信号蓝色部分与 RGB 信号亮度值之间的差异。

视频

视频编码

  • 音频压缩主要是去除冗余信息,从而实现数据量的压缩。其实视频压缩也是通过去除冗余信息来进行压缩的
  • 相较于音频数据,视频数据有极强的相关性,也就是说有大量的冗余信息,包括空间上的冗余信息和时间上的冗余信息。使用帧间编码技术可以去除时间上的冗余信息,具体包括以下几个部分。
    • 运动补偿:运动补偿是通过先前的局部图像来预测、补偿当前的局部图像,它是减少帧序列冗余信息的有效方法。
    • 运动表示:不同区域的图像需要使用不同的运动矢量来描述运动信息。
    • 运动估计:运动估计是从视频序列中抽取运动信息的一整套技术。使用帧内编码技术可以去除空间上的冗余信息。

ISO 视频压缩标准(Motion JPEG 即 MPEG):

  • Mpeg1(用于 VCD)
  • Mpeg2(用于 DVD)
  • Mpeg4 AVC(现在流媒体使用最多的就是它了)
  • ITU-视频压缩标准
  • H.261
  • H.262
  • H.263
  • H.264 (集中了以往标准的所有优点,并吸取了以往标准的经验)
  • h.265

现在使用最多的就是 H.264 标准,H.264 创造了多参考帧、多块类型、整数变换、帧内预测等新的压缩技术,使用了更精细的分像素运动矢量(1/4、1/8)和新一代的环路滤波器,这使得压缩性能得到大大提高,系统也变得更加完善

编码概念:
IPB 帧
视频压缩中,每帧都代表着一幅静止的图像。而在进行实际压缩时,会采取各种算法以减少数据的容量,其中 IPB 帧就是最常见的一种。

  • I 帧:关键帧。压缩率低,可以单独解码成一幅完整的图像。
  • P 帧:参考帧。压缩率较高,解码时依赖于前面已解码的数据。
    • P 帧需要参考其前面的一个 I 帧或者 P 帧来解码成一张完整的视频画面。
  • B 帧:前后参考帧。压缩率最高,解码时不光依赖前面已经解码的帧,而且还依赖它后面的 P 帧。换句话说就是,B 帧后面的 P 帧要优先于它进行解码,然后才能将 B 帧解码。
    • B 帧则需要参考其前一个I 帧或者 P 帧及其后面的一个P 帧来生成一张完整的视频画面,所以P 帧与 B 帧去掉的是视频帧在时间维度上的冗余信息

H.264 的 IDR 帧与 I 帧的理解

  • IDR 帧(instantaneous decoding refreshpicture) IDR 帧与 I 帧的区别
  • IDR 帧就是一种特殊的 I 帧,即这一帧之后的所有参考帧只会参考到这个 IDR 帧,而不会再参考前面的帧。在解码器中,一旦收到一个 IDR 帧,就会立即清理参考帧缓冲区,并将 IDR 帧作为被参考的帧。

因为 H.264 采用了多帧预测,所以 I 帧 之后的 P 帧有可能会参考 I 帧之前的帧,这就使得在随机访问的时候不能以找到 I 帧作为参考条件,因为即使找到 I 帧,I 帧之后的帧还是有可能解析不出来

I 帧、P 帧、B 帧 的说明(编码器工作):

  • 帧率 25fps( 指每秒 25 个 P 帧或 B 帧)
  • 视频画面变化不大情况下, 每间隔 25 个参考帧 出 一个 I 帧
  • 视频画面变化较大(如:电影场景切换)时,会立马 出一个 I 帧, 用于更换视频场景及后面的参考帧处理解码

视频播放器

视频协议

  • 只有在有网络时通过浏览器或者移动端APP才能看到的视频。
  • 常见的直播流协议有:RTMP、RTSP、HTTP 等;
  • 常见的点播协议有:http-MP4、http-FLV、HLS 等。
  • 在连接视频协议时,除了音视频频流和metadata之外,可能还会携带播放的信令

视频容器

  • 通常说的视频的格式,比如 .mp4, .mov, .wmv, .m3u8, .flv 等等被称为 容器 【container】。
  • 含了video数据、audio数据、metadata(用于检索视音频payload格式等信息)。
  • 每个格式的封装格式不一样,比如FLV格式的基本单元是Tag,而MP4格式的基本单元是Box,辅助的 meta信息用于检索找到对应的原始数据。

编解码器

  • Mpeg2, H.264, H.265等视频编码标准被称为 编解码器 ( codec)。
  • 一个视频格式比如 mp4可以使用任何标准化的压缩算法,这些信息都会被包含在一个视频文件的meta信息中来告诉播放器该用什么编解码算法来播放。

所以通过以上只是,我们基本知道,一个视频播放器的 播放文件的几个步骤

播放本地文件则不需要解协议

  1. 解封装
  2. 解码视音频
  3. 视音频同步

播放一个互联网上的视频文件

  1. 解协议 [rtmp\rtsp\http-flv\hls]
  2. 解封装(容器) [mp4\ts\avi]
  3. 解码视音频
  4. 视音频同步

回到上面,分析 ws-avc-player 的解题思路是

核心思路:

  1. 通过websock 接收 arraybuffer,创建缓冲器

  2. 通过缓冲器 借助 浏览器api: requestAnimationFrame 一帧一帧 解码绘制

  3. 借助 Broadway/Player库( WebAssembly + FFmpeg )进行 视频解码

分析

  1. 没有使用 视频协议 导致 流量带宽 消耗大

  2. 在远端编码阶段没有 使用容器 包裹音频数据,且 接收端 没有 音频解码操作

    增加音频可能需要单独的 websocket 接收 音频数据 进行解码 播放,而且还存在 音视频同步问题

  3. tcp 的 传输方式 延迟 无法避免

  4. 团队研发能力不足,无法进行二次开发

综上,选择了放弃该方案。

最终落地 使用 webrtc 全家桶

  • 编码端 解码端 统一解决方案
  • 低延迟(RTP/RTCP 基于udp)
  • 自适应码率
  • 稳定传输
仍然存在延迟,尚且需要优化。