Android 直播技术基础知识
直播原理
通过计算机上的音视频输入设备或者手机端摄像头和麦克风实时录制的音视频流,编好码后通过直播协议将数据包实时发送给服务器端,服务器端通过流媒体协议把实时流分发出去,其他终端通过直播协议实时请求数据包,并进行解码播放。
直播架构
主播架构主要分 3 块:这其中不只是播放音视频,还可以做一些实时美颜和滤镜效果。
- 采集数据推流过程,包括对数据流编码,通过流媒体协议传输到服务器上。
- 服务器端收到推流数据后,进行内容分发及中间转存处理。
- 播放器进行拉流操作。
直播过程
主要涉及采集数据、渲染处理、编码数据、推流、CDN 分发、拉流、播放流数据等。
采集数据
采集数据时从不同平台的设备中获取原始音视频数据,用于美颜或编码处理。数据采集涉及音频采集和图像采集。
采集内容
1、音频采集
音频数据既能与图像结合组成音视频数据,也支持单纯的音频数据输出(如电台)。音频的采集过程主要是通过设备设置采样率、采样数,将音频信号采集为 PCM 编码的原始数据,然后编码压缩成 MP3、AC3 等封装格式的数据分发出去。常见的音频封装格式有 MP3、AAC、OGG、WMA、Opus、FLAC、APE、M4A 和 AMR 等。
音频采集和编码面临的主要挑战在于去噪、回声消除(AEC)、静音检测(VAD)和各种音效处理等。
在音频采集阶段,参考的主要技术参数如下:
- 采样率(Samplerate):采样率越高,数据量越大,音质越好。需要注意的是,并不是采样率越高,效果越好,不同声音效果的采样率有一定的阈值。
- 位宽:表示一次能传递的数据宽度。就像公路的车道宽度有双向四车道、双向六车道等,车道越多一次能通过的汽车就越多,位宽越大,一次性能处理的数据就越多。音频采样过程中常用的位宽值有 8 位或者 16 位。
- 声道数(Channel):指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,声道数是声音录制时的音源数量或回放时相应的扬声器数量。声道数为 1 和 2 分别被称为单声道和双声道,是比较常见的声道参数。
2、图像采集
图像采集就是通过摄像头或者可以采集图像的设备,获取一段时间内的图像内容。如手机摄像头采集的 NV21(一种 YUV 格式)格式数据,然后经过编码压缩成 H.264 等格式的数据,随后可以编码成不同的封装格式(如 FLV)传递或者直接通过流媒体协议(如 RTMP 协议)传递到服务器端。
在图像采集阶段,参考的主要技术参数如下:
- 图像格式:通常采用 YUV 格式存储原始数据信息,其中包含用 8 位表示的黑白图像灰度值,以及可由 RGB 共 3 种色彩组成的彩色图像。
- 传输通道:指数据在传输时所利用的媒介,包括获取模块、数据类型模块及传输模块。比如可以使用 TCP 传输或者 UDP 传输。
- 分辨率:代表图像中存储的信息量,指每英寸图像内有多少个像素,图像分辨率的表达方式为 ”水平像素数 X 垂直像素数“。
- 采样频率(采样率):指每秒从连续信号中提取并组成离散信号的采样个数,如采样 720 像素视频,还是 540 像素视频,不同的采样个数对应的分辨率不一样,文件大小也不一样。
- fps:指画面每秒传输的帧数,通俗地将就是动画或视频的画面数。直播一般设置为 15~20fps。
在实际开发中,常常要调整以上的一些参数,如 fps、分辨率,以获得不错的直播效果。
采样源
1、摄像头采集
对于视频内容,主要通过摄像头和专业摄像机进行采集。
2、屏幕录制
一般可调用 Android 系统的 API 来捕捉屏幕进行录制。在一些音视频会议中,常常使用开源的桌面推流工具 OBS 进行屏幕录制和推流(使用时可将采集的内容分为图像和音频。图像的采集源包含摄像头、屏幕录制或者本地的视频文件,甚至其他需要重新定义和实现的采集源。音频的采集源包含麦克风、系统声音或者本地音频文件,当然也可以为它定义别的采集源。这样设计最大的好处在于,可以以轻量的设计方式支持丰富的采集源,而采集源的具体实现也可以交给使用者)。
渲染处理
这里所说的渲染处理,主要是对相机中采集来的数据进行二次处理。市面上比较好的美颜厂商有商汤、FaceUnity 等,而自己做美颜封装,可用的开源库主要是 GPUImage。
美颜的基本概念:通过一定的算法对原始图像数据进行二次处理并强化图像效果,不限于去掉不协调区域、边缘检测等。
GPU 工作原理:GPU 指图像运算工作的微处理器,GPU 主要利用显卡对图像的顶点坐标,通过图元组配,进行光栅化、顶点着色、片元着色等一系列管线操作。
OpenGL ES(Open Graphics Library for Embedded System,开源嵌入式系统图形处理框架):一套图形与硬件的接口,用于把处理好的图像显示到屏幕上。
GPUImage:是一个基于 OpenGL ES 2.0 图像和视频处理的跨平台框架,提供了各种各样的图像处理滤镜,支持相机和摄像头的实时滤镜,内置 120 多种滤镜效果,并且能够自定义图像滤镜。
滤镜处理的原理:就是把静态图像或者视频的每一帧进行图形变换后显示出来。它的本质是像素点的坐标和颜色变化。
GPUImage 处理画面的原理如下:
GPUImage 采用链式方式处理画面,通过 addTarget 函数为链条添加每个环节的对象,处理完一个 target,就会把上一个环节处理好的图像数据传递给下一个 target 去处理,这被称为 GPUImage 处理链。比如,墨镜原理,从外界传来光线,会经过墨镜过滤,再传给眼睛,这样就能感受到大白天也会乌黑一片了。
一般的 target 可分为两类,一种是中间环节的 target,一般是各种滤镜,即 GPUImageFilter 或者其子类,另一种是最终环节的 target,GPUImageView,用于显示到屏幕上,或者是 GPUImageMovieWriter,写成视频文件。
GPUImage 处理主要分为 3 个环节,即 Source(视频、图像源) -> filter(滤镜)-> final target(处理后的视频、图像)。
GPUImage 的 Source 都继承自 GPUImageOutput 的子类,作为 GPUImage 的数据源,就好比外界的光线,作为眼睛的输出源。Source 包括如下几种:
- GPUImageVideoCamera:用于实时拍摄视频。
- GPUImageStillCamera:用于实时拍摄照片。
- GPUImagePicture:用于处理已经拍摄好的图像,比如 png、jpg 文件。
- GPUImageMovie:用于处理已经拍摄好的视频,比如 mp4 文件。
GPUImage 的 filter 一般是 GPUImageFilter 类或者其子类,这个类继承自 GPUImageOutput ,并且遵守 GPUImageInput 协议,这样既能流进又能流出。这就好比墨镜,光线经过墨镜的处理,最终进入我们的眼睛。
GPUImage 的 final target 一般是 GPUImageView、GPUImageMovieWriter,这就好比我们的眼睛,最终输入目标。
编码数据
1、视频编码的意义
视频编码指的是通过特定的压缩技术,将某个视频格式文件转换成另一种视频格式文件的方式。这里要重点考虑的是压缩,如当一个普通文件太大时,可以用一些压缩工具压缩后传输,以提高效率。视频压缩也是一样的,采集到的视频数据一般较大,经过 H.264 编码压缩后,体积就会减小很多,这样在传输过程中可以节省很多网络宽带资源。
2、压缩原理
其核心思想就是去除冗余信息,如下所述。
- 空间冗余。在很多图像数据中,像素间在行、列方向上都有很大的相关性,相邻像素的值比较接近或者完全相同,这种数据冗余叫做空间冗余。(相邻区域颜色相近区域)
- 时间冗余。在视频图像序列中,相邻两帧图像数据有许多共同的地方,这种共同性被称为时间冗余,可采用运动补偿算法来去掉冗余。
- 视觉冗余。视觉冗余度是相对于人眼的的视觉特性而言的,人类视觉系统对图像的敏感性是非均匀和非线性的,即人眼观察不到图像中的所有变化。(去除人眼不敏感的)
- 信息熵冗余。信息熵指一组数据所携带的信息量,信息熵冗余指数据所携带的信息量少于数据本身,从而反映出数据冗余。
- 结构冗余。在有些图像的纹理区,图像的像素值存在着明显的分布模式。
- 知识冗余。对许多图像的理解与某些先验知识有相当大的相关性。这类规则的结构可由先验知识和背景知识得到,此类冗余被称为知识冗余。(去除基础知识)
3、常用压缩编码方法分类
除了空间冗余和时间冗余的压缩,主要还有编码压缩和视觉压缩。下面是一个编码器主要的流程图。
可以看到两个过程的主要区别就是第一步不相同,其实这两个流程也是结合在一起的,我们通常说的 I 帧和 P 帧就分别采用了帧内编码和帧间编码。
- 预测:通过帧内预测和帧间预测降低视频图像的空间冗余和时间冗余。
- 变换:通过从时域到频域的变化,去除相邻数据之间的相关性,即去除空间冗余。
- 量化:通过用更粗糙的数据表示精细的数据来降低编码的数据量,或者通过去除人眼不敏感的信息来降低编码数据量。
- 熵编码:根据待编码数据的概率特性降低编码冗余。(熵编码,11112222 = 8个字节,4142 =4 个字节。)
推流
推流协议
1、RTMP(Real Time Messaging Protocol):实时消息传输协议。该协议基于 TCP,是一个协议族,包括 RTMP 基本协议以及 RTMPT、RTMPS、RTMPE 等多个变种协议。RTMP 协议是一种被设计用来进行实时数据通信的网络协议,主要用在 Flash 平台和支持 RTMP 协议的流媒体/交互服务器之间进行音视频和数据通信。支持该协议的软件包括 Adobe Media Server、Ultrant Media Server、Red5 等。RTMP 是目前主流的流媒体传输协议,广泛应用于直播领域。
优点
- 对 CDN 友好,主流的 CDN 厂商都支持该协议。
- 协议简单,在各平台上实现容易。
缺点
- 基于 TCP,传输成本高,在弱网环境丢包率高的情况下问题明显。
- 不支持浏览器推送。
- Adobe 私有协议,Adobe 已经不再更新该协议。
2、WebRTC(Web Real Time Communication):网页即时通信,是一个支持 Web 浏览器进行实时语音对话或视频对话的 API。它于 2011 年 6 月 1 日开源,并在 Google、Mozilla、Opera 的支持下被纳入万维网联盟的 W3C 推荐标准。目前其主要应用于视频会议和连麦中。
优点
- W3C 标准,主流浏览器支持程度高。
- Google 在背后支撑,并针对各平台有参考实现。
- 底层基于 SRTP 和 UDP,在弱网情况下优化空间大。
- 可以实现点对点通信,通信双方延时低。
缺点
- ICE、STUN、TURN 等传统 CDN 没有提供类似的服务。
3、基于 UDP 的私有协议:有些直播应用会使用 UDP 作为底层协议开发自己的私有协议。由于 UDP 在弱网环境下的优势,因此通过一些定制化的调优可以达到比较好的弱网优化效果,但同样因为其是私有协议,故势必有现实问题。
优点
- 有更多的空间进行定制化优化
缺点
- 开发成本高。
- 对 CDN 不友好,需要自建 CDN 或者和 CDN 达成协议。
- 独立作战,无法和社区一起演进。
推流过程
推流过程就是把编码后的数据打包并通过直播协议发送给流媒体服务器的过程。
- 经过输出设备得到原始的采样数据—视频数据(YUV)和音频数据(PCM)。
- 使用硬编码(MediaCodec)或软编码(FFmpeg)来编码压缩音视频数据。
- 分别得到已编码的 H.264 视频数据和 AAC 音频数据。
- 将已编码的音视频数据封装成不同的视频封装格式的数据文件(如 FLV、TS、MPEG-TS)。
- 使用 HLS 协议的时候加上这一步(HLS 分段生成策略及 m3u8 索引文件。
- 通过流媒体协议上传到服务器。
- 服务器通过相关协议对内容进行分发。
推流优化
在推流过程中经常会遭遇网络不好、断流的情况,所以需要一定的策略。在推流端 ping 视频中心地址,测试是否有丢包现象。同时在网络抖动时,需要动态调整一些参数以便推流。在网络断后重连时,需要重新优先发送音频数据,保证用户能听到声音。视频数据随后到达,使观众看到画面变化,并逐步回归到音视频同步。
CDN 分发
CDN 技术原理
CDN(Content Delivery Network):内容分发网络,是一个策略性部署的整体系统,主要用来解决由于网络宽带小、用户访问量大、网点分布不均匀等造成的用户访问网站速度慢的问题。这中间会有很多 CDN 节点,简单一点理解就相当于让计算机选择最优网络。具体实现是,通过在现有的网络中增加一层新的网络架构,将网站内容发布到离用户最近的网络节点上。这样用户可以就近获取所需的内容,解决之前的网络拥塞、访问延时高的问题,提高用户体验。
CDN 分发过程:Web 服务器 -> 某个 CDN 节点 -> 客户端
在 CDN 直播中,对不同流媒体所使用的的节点和协议做了区分,使得网络拥塞和访问延时减少,带宽得到良好的控制等。在 CDN 直播中常用的流媒体协议包括 RTMP、HLS、HTTP-FLV 等。
直播推流到 CDN 后,不同协议的分发过程:主播(RTMP)-> 某个 CDN 节点 -> 观众(RTMP、HLS、HTTP-FLV 等)
RTMP(Real Time Messaging Protocol)基于 TCP 协议,是由 Adobe 公司为 Flash 播放器和服务器之间进行音视频传输开发的开放协议。HLS(HTTP Live Streaming)基于 HTTP 协议,是 Apple 公司开放的音视频传输协议。HTTP-FLV 则将 RTMP 封装在 HTTP 协议之上,可以更好地穿透防火墙等。
CDN 的常用架构
CDN 架构比较复杂,而且不同的 CDN 厂商也在对其架构进行不断的优化,所以对这些架构不能统一而论。这里只对一些基本的架构进行简单的剖析。
CDN 主要包含源站、缓存服务器、智能 DNS、客户端等几个主要组成部分。
- 源站:指发布内容的原始站点。添加、删除和更改网站的文件都是在源站上进行的,另外缓存服务器所抓取的对象也全部来自源站。对于直播来说,源站为主播客户端。
- 缓存服务器:是直接提供给用户访问的站点资源,由一台或数台服务器组成。当用户发起访问时,其访问请求被智能 DNS 定位到离他较近的缓存服务器。如果用户所请求的内容刚好在缓存里面,则直接把内容返还给用户,如果访问所需的内容没有被缓存,则缓存服务器向邻近的缓存服务器或直接向源站抓取内容,然后返还给用户。
- 智能 DNS:是整个 CDN 技术的核心,它主要根据用户的来源,以及当前缓存服务器的负载情况等,将其访问请求指向离用户比较近且负载较小的缓存服务器。通过智能 DNS 解析,让用户访问同服务商、负载较小的服务器,可以缓解网络访问慢的情况,达到加速的作用。
- 客户端:即发起访问的普通用户。对于直播来说,就是观众(客户端),例如手机客户端、PC 客户端。
整个流程描述如下:
- 主播开始进行直播,向智能 DNS 发送解析请求。
- 智能 DNS 返回最优 CDN 节点的 IP 地址。
- 主播端采集音视频数据,发送给 CDN 节点,CDN 节点进行缓存等处理。
- 观众要观看这个主播的视频,向智能 DNS 发送解析请求。
- 智能 DNS 返回最优 CDN 节点的 IP 地址。
- 观众向 CDN 节点请求音视频数据。
- CDN 节点同步其他节点的音视频数据。
- CDN 节点将音视频数据发送给观众。
CDN 的缺点
大概了解了 CDN 的技术原理后,在做直播选型时,还需要了解一个方案的优缺点。总的来说,CDN 主要有如下缺点。
1、播放延时(网络延时)
网络延时这里指的是从主播端采集到观众播放之间的时间差。这里不考虑主播端采集对视频进行编码的时间,以及观众端播放视频进行解码的时间,仅考虑网络传输中的延时。
2、网络抖动
指数据包的到达顺序、间隔等与发出时不一致。比如,发送 100 个数据包,每个包间隔 1s 发出。结果第 30 个包在传输过程中遇到网络拥塞,造成包 30 不是紧跟着包 29 到达的,而是延迟到包 90 后面才到达。在直播中,这种抖动的效果实际上跟丢包是一样的。因为你不能依照接收顺序把内容播放出来,所以这样会造成失真。网络抖动会造成播放延时相应地增大。如果网络中抖动得较大,会造成播放卡顿等现象。
3、网络丢包
CDN 直播中用到的 RTMP、HLS、HTTP-FLV 等协议都建立在 TCP 协议的基础之上。TCP 的一个很重要的特性是可靠性,即不会发生数据丢失的问题。为了保证可靠性,TCP 在传输过程中有三次握手。首先客户端会向服务端发送连接请求,服务端同意后,客户端会确认这次连接,这就是三次握手。接着,客户端开始发送数据,每次发送一批数据,得到服务器端的 ”收到“ 确认后,继续发送下一批。TCP 协议为了保证数据传到目的地,会有自动重传机制。如果传输中发生了丢包,没有收到对方端发出的 ”收到“ 信号,那么就会自动重传丢失的包,一直到超时。
由于互联网的网络状况是变化着的,加之主播端的网络状况是无法控制的,因此当网络中的丢包率开始升高时,重传会导致延时不断增大,甚至导致不断尝试重连等情况,这样不能有效缓存,在严重的情况下甚至会导致观众端无法观看视频。
拉流
根据协议类型(如 RTMP、RTP、RTSP、HTTP 等),与服务器建立连接并接收数据。
- 解析二进制数据,从中找到相关流信息。
- 根据不同的封装格式(如 FLV、TS)解复用。
- 分别得到已编码的 H.264 视频数据和 AAC 音频数据。
- 使用硬解码(MediaCodec)或软解码(FFmpeg)来解压音视频数据。
- 经过解码后得到原始的视频数据(YUV)和音频数据(PCM)。
- 因为音频和视频解码是分开的,所以在解码后,需要做音视频同步。通常有一些音视频同步算法。如在 FFmpeg 中,以音频作为参考时钟,视频用于比较当前时钟和音频时钟的差值,如果快了,就需要增大延时,以便下一帧显示得晚些,如果慢了,就需要减少延时,加快显示下一帧。
- 最后把同步的音频数据发送到耳机或外放设备上播放,将视频数据发送到屏幕上显示。
了解了播放器的播放流程后,可以优化首帧显示时间。从第二点入手,通过预设解码器类型,省去探测文件类型的时间。从第五点入手,缩小视频数据探测范围,同时也意味着减少了需要下载的数据量,特别是在网络不好的时候,较少下载的数据量能为启动播放节省大量的时间,在检测到 I 帧数据后就立马返回并进入播放解码环节。
播放流数据
播放流数据,一般涉及几个过程。
- 首先进行 access 操作,也就是获取数据流。
- 然后进行 demux 操作,也就是解复用,将数据流分离成音频流和视频流。
- 接着将音频流送入音频解码器,将视频流送入视频解码器。
- 最后进行音视频同步并输出。
通常直接使用第三方播放器,仅仅了解 API 调用就行,其他的则交由播放内核去做。如在 Android 上使用 MediaPlayer,常规使用方式如下。
1 | public void startPlayUri(Uri uri){ |
基本上第三方播放器(如 VLC、IjkPlayer)对外提供的接口都是从 Android 的 MediaPlayer 接口扩展来的。对于应用来说,只要把 URL 发送给播放器并调用 prepareAsync 函数,然后等待 onPrepared 回调成功后,就可以调用 start 函数起播了。
关于软硬解码的选择。
考虑到 Android 平台的各种兼容性问题,需要根据不同手机的解码能力来进行硬解码和软解码的选择。这里也介绍一些经验,但根本问题是,没有一个通用方案能最优适配所有操作系统和机型。
- 硬解码:推荐 Android 4.1.2(API 16)或以上版本使用硬解码,而 4.1.2 以下版本使用软解码。
- 软解码:主要利用 CPU 执行大量运算来解码。虽然这样牺牲了功耗,但是在部分细节方面表现较优,且可控性强,兼容性也强,出错情况少,在硬解码无能为力的情况下,软解码也不失为一种不错的选择。
备注
参考资料: