前言
近年来,随着HTML5技术普及、移动设备迅猛增长和流量资费不断下降,视频已成为人们了解世界、获取知识的主要途径之一。现在音视频播放器已随处可见,但这个过程到底如何实现?播放器的原理又是什么?或者是作为生产者,应该依据哪些标准来选择适合自己的播放器?UCloud将基于多年的实践经验,为客户做出相应解答或建议。
一.播放器功能架构
首先来看常规播放器的功能架构:
应用层:UI/统计/DRM(数字版权保护)/多码率/弹幕/广告等。
底层:数据接收模块/解复用模块/音视频解码、滤镜、渲染模块/字幕/应用层结合功能:DRM、多码率/统计等。
播放器的功能比较多,本文就不再一一赘述,下面将重点讲解多媒体引擎模块。
二.多媒体引擎
作为播放器的核心部分,多媒体引擎主要负责音视频数据的加载、处理和展现。以FFmpeg为例,它的基本运作流程如下图所示:
(图:多媒体播放流程图)
我们详细了解一下每个环节:
1、数据接收(Source)
作为数据入口,这里的文件可以通过本地文件(File://协议开头)输入,也可以通过网络协议(如HTTP、RTMP、RTSP等)输入。在找到对应的传输协议之后,FFmpeg会根据协议特性与本机或服务器建立连接并获取到流数据。
2、解复用(Demux)
通过对文件的特征码分析,可以找到文件的封装格式,如常见的MP4、FLV、TS、AVI等。根据封装格式的标准对其拆封,可以得到编码的音视频数据,一般称之为“packet”。
3、解码(Decode)
解码器初始化时,利用之前源数据分析获得的音视频信息,分别设置对应的音频解码器和视频解码器。目前互联网中比较常见的音频编码方式有AAC(Advanced Audio Coding)、MP3,视频编码方式有H.264、H.265。对packet分别进行解码后,音频解码获得的数据是PCM(Pulse Code Modulation,脉冲编码调制)采样数据,一般称为“sample”。视频解码获得的数据是一幅YUV或RGB图像数据,一般称为“picture”。
4、音视频同步(Synchronizing)
音视频解码时是两个独立线程,因此获得的音视频数据是分开的,并无任何关联。理想状态下,音视频按照自己固有频率渲染输出就能达到音视频同步的效果。但是在现实中,断网、弱网、丢帧、缓冲、音视频不同的解码耗时等情况都会妨碍实现同步,很难达到预期效果,所以要保证视频内容和音频内容对得上,必须做音视频同步。
5、音视频渲染(Render)
经过音视频同步调整之后,需要把sample和picture分别输送给声卡和显卡,这部分工作建议由成熟的库来完成。常见的音频库有SDL、OpenAL/OpenAL ES、DirectSound、ALSA(Advanced Linux Sound Architecture)、OSS(Open Source System)等;视频库有SDL、OpenGL/OpenGL ES、DirectDraw、FrameBuffer等。
经过以上5个步骤,基本的播放流程就完成了。
三.各平台视频技术概况
要实现全平台覆盖的播放器,自然少不了技术支撑。这里主要讨论Web、Android、iOS上的音视频技术,讲述一部分音视频常用方案。
1、Web-HTML5
技术语言:JavaScript,HTML/CSS
使用环境:所有可以支持HTML5 Video标签的终端浏览器(PC Windows/Mac/Linux/Unix、Android、iOS)即可播放视频,目前覆盖率已达95%。
相关方案:
1.1 标签
从代码示例来看,HTML5的Video标签非常简单易用,controls= " controls "可以使用浏览器默认的播放器UI(不同浏览器UI不一样),如果不对controls赋值,则可以通过CSS和Video消息来实现自定义UI及控制。
1.2 MSE(Media Source Extensions)
Media Source Extensions (MSE) 是一个主流浏览器支持的新Web API,而且是W3C标准,允许JavaScript动态构建 和 媒体流。它定义了对象, 允许JavaScript传输媒体流片段到一个HTMLMediaElement。
通过使用MSE,可以动态地修改媒体流而不需要任何插件。这使前端JavaScript可以做更多事情, 比如可以在JavaScript进行转封装、处理,甚至转码,因此像ABR(Adaptive Bitrate)、hls.js、dash.js、flv.js也是基于这点来实现的。
虽然MSE不能让流直接传输到media tags上, 但是MSE提供了构建跨浏览器播放器的核心技术, 让浏览器通过JavaScript API来推音视频到media tags上,目前MSE的覆盖率已达77.93%。
2、Web-Flash
技术语言:ActionStript
使用环境:安装了FlashPlayer的浏览器(PC Windows/Mac/Linux/Unix、Android、iOS)即可播放视频。曾经是互联网时代早期富媒体老大的Web-Flash,因HTML5普及已慢慢淡出人们的视野,现在更多是作为HTML5播放器的互补方案来使用。
相关方案:
2.1 FLVPlayBack
Flash自带封装好的播放器,跟HTML5的浏览器默认播放器差不多,系统了一些播放器UI样式供选择,简单几句代码就可以快速构建精美的播放器,但是灵活度较差。
2.2 NetStream
Flash处理多媒体的核心类,它提供了appendbytes这个API,允许外部传入FLV封装格式的数据进来播放,类似于HTML5的MSE。同理,可以用此接口实现视频的转封装、处理、转码等操作,再配合RTMFP协议,还可以实现P2P的视频传输播放。
2.3 CrossBridge & FFmpeg
曾几何时,Flash还有个叫CrossBridge(又叫Alchemy、Flascc)的黑科技,允许在Flash规定范围内跑自己编写的C/C++代码,这样就彻底提升了运算效率,如果配合FFmpeg的多媒体处理能力,可能会有意想不到的效果。
3、Andorid
技术语言:Java、JNI(C/C++)
使用环境:Andorid系统(Andorid phone、Android pad、Andorid TV、Android Box等)。
相关方案:
3.1 MediaPlayer
Android提供的默认多媒体播放器,可以用来播放本地或网络上的音频、视频数据,主要负责解协议、解码,但不包含显示部分。显示部分需要配合SurfaceView或者SurfaceTexture来处理。
3.2 MeidaCodec (API 16+ ,Jelly Bean 4.1.x)
Android提供的硬编解码类,第一次使用是在Android 4.1版本(API16),主要对音视频数据进行编码或解码操作。在Android 4.3(API18)中,MediaCodec被扩展为包含一种通过 Surface 提供输入的方法(即createInputSurface),这允许输入来自于相机的预览或是经过OpenGL ES呈现。
3.3 JNI & FFmpeg
JNI是Java Native Interface的缩写,一开始是为了本地已编译语言,尤其是C和C++而设计的,但并不妨碍使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性,但有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库与硬件、操作系统进行交互,或者为了提高程序的性能。JNI配合FFmpeg这个强大引擎,使Android平台具备了较好的多媒体处理能力。
4、iOS
- 技术语言:Objective C、C/C++、Swift
- 使用环境:iOS系统(iphone、ipad)
相关方案:
4.1 AVFoundation.framework:AVPlayer、AVPlayerLayer
AVPlayer类只包含音视频的数据接收、解码、处理工作,不包含显示部分,需要配合AVPlayerLayer来使用,可定制化强度大、灵活,消息也比较丰富,官方推荐使用,取代了MPMoviePlayerController的位置。
4.2 AVFoundation.framework:AVPlayerViewController
提供了默认的可视化控制界面,整合了一个完整的播放器,可以作为控制器进行操作显示。官方推荐使用,取代了MPMoviePlayerViewController的位置。
4.3 MediaPlayer.framework:MPMoviePlayerController
iOS的基础播放器,使用几个简单的API就能完成视频文件播放,其内部已包含数据接收、解码功能,如果要在UI中展示视频,需要将view属性添加到界面中,播放器UI方面需要自行开发。iOS 9.0以后,苹果官方已弃用。
4.4 MediaPlayer.framework:MPMoviePlayerViewController
iOS的基础播放器视图控制器,MPMoviePlayerViewController在MPMoviePlayerController基础上封装了一层View,默认全屏模式展示,弹出后自动播放,作为模态窗口展示时如果点击“Done”按钮会自动退出模态窗口等,有一套自己的UI。iOS 9.0以后苹果官方已弃用。
4.5 VideoToolbox.framework
VideoToolbox是一个底层框架,iOS 8.0以后,官方正式放开了硬件编解码API,它为视频压缩和解压缩提供服务,并存储在缓冲区corevideo像素栅格图像格式之中。这些服务以会话对象的形式提供(压缩、解压和像素传输),应用程序不需要直接访问硬件编码器和解码器相关内容,而只需要直接使用videotoolbox即可实现编解码。iOS设备在硬件编解码这块的质量有一定保证,可以优先使用硬编解码,FFmpeg软解码为互补方案。
4.6 FFmpeg
因Objective-C支持 C/C++混合编译,所以很容易就可以把FFmpeg使用起来,让iOS具备强大的多媒体处理能力。
5、小结
从HTML5、Flash、iOS、Android四个平台上看,它们似乎都具备音视频播放能力,但它们之间到底有些什么差别?我们应如何选择合适自己的方案?
先对比一下播放方面的技术差异:
我们尝试把产品场景分为四种,以下是对各自的建议:
四、几个常见问题
做一个播放器远没有想象的那么简单,来看一下播放器的几个常见问题。
1、 怎么做音视频同步?
对于系统封装好的播放器,则无须关心这个问题,但如果要实现一个完全可控的播放器,从数据接收->解复用->解码->音视频同步->渲染都需要去干预。处理音视频同步的原因已在前面章节讲过了,这里着重讲实现原理。
做音视频同步之前,先补充个知识点,DTS与PTS(I帧、P帧、B帧请自行补充)。
- DTS(Decode Time Stamp):解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
- PTS(Presentation Time Stamp):显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
视频编码中必存在I帧,可能存在B帧、P帧,B帧是双向参考帧,因此会打乱帧的解码和显示顺序,因此引入了DTS和PTS来应对这种情况。普遍情况下B帧压缩率较I帧、P帧高,在点播视频中比较常见,直播视频中一般没有B帧,在此情况下,DTS和PTS应该相同。音频编码因为没有参考帧的概念,是不需要DTS的。
在清楚了DTS和PTS之后,可见PTS对音视频同步起关键作用,音视频常见的同步方式有三种,分别是:以音频为主线同步、以视频为主线同步和以外部时钟同步。第一种最为常见,也是比较符合人类视觉听觉感官的方式(音频变速会导致变声),同时音频的播放速率比较固定,比较容易作为主线管理,其同步过程总结如下:
a) 解码器从输入的数据封装中解出音频PTS、视频PTS;
b) 创建音频播放线程,音频按固有的采样频率播放;
c) 创建视频刷新线程,按一定频率(10ms)定时检测;
d) 通过音频、视频的时间基(time_base)换算成相同时间基准的PTS;
e) 在视频刷新线程中计算当前真实的音频PTS、视频PTS:
- currentRealVideoPTS = 当前准备显示的视频帧PTS + 当前视频帧流逝系统时间;
- currentRealAudioPTS = 最新已播放的音频帧PTS + 当前音频帧流逝系统时间;
f) 计算currentRealVideoPTS - currentRealAudioPTS的差值diff;
g) 若diff <= 0,说明视频显示慢了,这时需要马上显示该帧数据;
- 若diff > 0,则视频酌情做适当的延时显示;
h) 一帧数据的音视频同步检测就此结束。
以上方法的核心理念是通过延时评估算法来控制视频帧慢放或快放,以此做音视频在合理范围内保持同步。
2、 怎么做到视频秒开?
有以下几点可以优化:
- a) 服务器端下发关键帧数据,保证播放器第一时间解码到有用的数据;
- b) 播放器可以在选择音视频解码器环节做优化,通过外部预设解码器相关参数来减少解码器类型探测耗时;
- c) 在业务层面上优化,使用HTTP-DNS服务做到精准调度,选择最优服务器获取播放数据;
- d) 提升音视频服务节点质量;
3、 怎么做到低延时?
延时这词是针对直播而言,先看直播系统的架构图:
从图中可以看出,可能产生延时的环节有:
a) 主播端推流至上传节点的延时;
可以通过接入HTTP-DNS为主播选择最优节点,减少传输延时。
b) 主播端发包的延时;
主播端应尽快发送本地数据至上传节点,对于弱网环境,应该有合理的控制策略,尽量避免大量过期数据阻塞在本地。
c) 上传节点到服务器集群的数据分发、转封装、转码等耗时;
分发的层级越多,延时则越大,这块也主要依赖于服务器间的网络状况。
d) 观众端到下载节点的延时;
可以通过接入HTTP-DNS为观众选择最优节点,减少传输延时。
e) 播放器本地存在buffer;
常规的播放器都会有本地缓存,合理的缓存设置可以为用户带来较好的观看体验,减少视频的卡顿次数。如果对延时有较高要求,可以减少buffer的缓冲时长,甚至可以把buffer去掉。
4、 播放器卡顿的原因有哪些?
再看上图,卡顿可能的原因有:
- a) 主播端推流与上传节点传输不顺畅;
- b) 服务器间传输出了问题;
- c) 观众与下载节点传输不顺畅。
五、总结
看完这些,相信大家对基础的播放流程及各平台上音视频处理技术已有初步了解,但是要做好多个平台的播放器并非易事,要做大量兼容性适配、容灾、性能调优、播放质量统计反馈、新功能迭代等工作。因此,一款好的播放器需要不断打磨,UCloud也将持续投入更多资源去支持该领域的运作。
作者简介
【刘宇峰,现任UCloud流媒体终端研发部经理,热爱多媒体行业和专注于Web、Android、iOS方面的技术,拥有近十年的互联网和多媒体领域的研发经验,现主要负责UCloud视频云SDK、直播云SDK的架构设计、研发管理和运营工作。此前,曾任职于腾讯,主要负责Web前端多媒体方面的研发工作。】