HTML5 Video 实现浅析

来源:互联网 发布:跑步软件咕咚 编辑:程序博客网 时间:2024/05/17 04:30

HTML5 Video实现浅析

——基于android4.1browser

1.     基本结构


先上一张基本结构图,图中展示了webkit中与HTML5 Video实现相关的一些类。

1.1  DOM Tree

Video标签在webkit内部对应于HTMLVideoElement类,该类的功能并不多,大多数的功能都在其父类HTMLMediaElement中。

1.2  Render Tree

HTMLVideoElement和HTMLMediaElement分别对应的Render节点是RenderVideo和RenderMedia。它们也一般的Render节点不同,它们只负责layout来确定位置信息,而并不需要绘制(虽然有paint函数,但实际并没有调用到)。

1.3  Bridge

MediaPlayer类指的是WebKit中定义的MediaPlayer类(而不是系统定义的),它只是一个桥接类,相关功能交由MediaPlayerPrivateInterface类实现,并将某些反馈信息回调给HTMLMediaElement。

1.4  Player

按照webkit的一贯风格,MediaPlayerPrivateInterface只是一个基类,具体需要不同的平台来提供具体实现。Android平台下为MediaPlayerPrivate类,它也是一个基类,分别由MediaPlayerVideoPrivate和MediaPlayerAudioPrivate来实现。很显然,它们分别对应Video和Audio的实现。

   上述是webkit内的类,下面再看看webkitporting之后外部的类结构:


可以看到,这些类大多数都是轻量级类,它们主要负责功能转发。最终实现类是android中的MediaPlayer类(请与前述MediaPlayer区别开来)。至于该MediaPlayer再怎么实现,已不在本文的讨论范围内。值得注意的是,HTML5VideoViewProxy类继承自Handler,它转发到VideoPlayer的消息使用了android的异步调用机制。

2.     操作控制

同过上述基本结构的介绍,我们可以想象一个播放器操作,如play,seek等,它们从HTMLMediaElement发出,最终一步一步到达android的Mediaplayer。下面是一个seek过程的调用堆栈:

#0  WebKit::HTML5VideoViewProxy::seek

#1 WebCore::MediaPlayerPrivate::seek

#2 WebCore::MediaPlayer::seek

#3  WebCore::HTMLMediaElement::seek

#4 WebCore::MediaControlTimelineElement::defaultEventHandler

当然基本流程是这样,有时候也不是一成不变的,比如play就有多种方式,多种流程来实现。具体问题具体分析,打一个断点一切流程都一目了然。


上图展示的是一个播放器状态转移情况,只有这么几种状态,还是很好理解的,这里就不赘述了。

3.     渲染流程

上述业务操作逻辑比较简单,我们更感兴趣的是video的显示过程,也就是渲染流程。前面说过,RenderVideo虽然有paint函数,但并没有派上用场。那是怎样完成渲染的呢?还需要从webkit的渲染结构说起。


这张图片所展示的内容大家应该都很熟悉,是webkit中的基础结构。但这还没有完,在硬件加速渲染下,还需要更多的结构来支持:


如果大家对这些结构不熟悉,可以参考:http://blog.csdn.net/milado_nju/article/details/7292131和http://blog.csdn.net/milado_nju/article/details/8900792 两篇文档。

GraphicsLayer也是一个基类,在android下的实现为GraphicsLayerAndroid,该类包含一个LayerAndroid类,才是硬件加速用到的Layer。当然LayerAndroid也是一个基类,Video对应的是VideoLayerAndroid类。可以简单这样理解,一个Video标签在硬件加速部分对应于一个VideoLayerAndroid类,用于显示Video内容。

那么我们看看VideoLayerAndroid是怎么创建的,如下堆栈:

#0 VideoLayerAndroid

#1 MediaPlayerPrivate

#2 MediaPlayerVideoPrivate

#3 WebCore::MediaPlayerPrivate::create

#4 WebCore::MediaPlayerPrivate::create

#5  WebCore::MediaPlayer::loadWithNextMediaEngine

#6 WebCore::MediaPlayer::load

#7 WebCore::HTMLMediaElement::loadResource

   可以看到,load操作引起了一系列初始化,其中VideoLayerAndroid由MediaPlayerPrivate来初始化。这个过程与GraphicsLayerAndroid没有关系,也就是说VideoLayerAndroid并没有与webkit的主体结构联系起来。事实上另有玄机:

#3 WebCore::GraphicsLayerAndroid::setContentsToMedia

#4  WebCore::RenderLayerBacking::updateGraphicsLayerConfiguration

#5 WebCore::RenderLayerCompositor::updateLayerCompositingState

#6  WebCore::RenderLayer::contentChanged (

#7  WebCore::RenderVideo::updatePlayer

#8 WebCore::HTMLMediaElement::mediaPlayerSizeChanged

#9  WebCore::MediaPlayer::loadWithNextMediaEngine

上述流程最终调用到GraphicsLayerAndroid::setContentsToMedia,会将VideoLayerAndroid设入GraphicsLayerAndroid,这样创建的VideoLayerAndroid就与webkit的主体结构联系在一起,成为硬件加速LayerAndroid树的一部分。

VideoLayerAndroid中有一个drawGL函数,就是该Layer的绘制函数。其中有一个非常重要的成员m_surfaceTexture, 它是一个SurfaceTexture类型。正是通过调用SurfaceTexture::updateTexImage来绘制的GL纹理。那么m_surfaceTexture成员是怎么得到的呢?看下面堆栈:

#0 WebCore::VideoLayerAndroid::setSurfaceTexture

#1 android::SendSurfaceTexture

#2 WebKit::HTML5VideoViewProxyDelegate::nativeSendSurfaceTexture

#3  WebKit::VideoPlayer::setBaseLayer

可见SurfaceTexture是在外部创建(事实上有HTML5VideoInline类创建),并传递给VideoLayerAndroid。另外setSurfaceTexture函数还会将VideoLayerAndroid的信息设置给VideoLayerManager类。

现在从webkit硬件加速的角度看,绘制需要的对象已经准备就绪。主要是video所对应的硬件加速层VideoLayerAndroid,以及绘制GL纹理所需要的类SurfaceTexture(通过updateTexImage函数来绘制)。

SurfaceTexture既然在webkit外部创建,那它在外部是怎样被使用的呢?可以看HTML5VideoInline::decideDisplayMode函数。先用SurfaceTexture构造一个Surface,然后将该Surface设置给MediaPlayer。MediaPlayer做的事情就是,从站点上下载video数据,并进行解码,解码后的结果输出给Surface,再经过SurfaceTexture:: updateTexImage来最终成为GL纹理。看下图:


该图大体上描述了整个HTML5 Video的控制,渲染的主要类的关系。该图中大部分内容前文已经介绍过了,那么我们关注一点,解码的结果怎样从surface传送到surfacetexture,因为只有内容在surfacetexture才能被绘制成GL纹理。

其实这个过程曾经深入研究过android surface系统的同学应该很熟悉。我在这里简单介绍一下。Surfacetexture创建的时候也会创建一个BufferQueue,这才是一个重要的类,它维护了一个共享内存的队列。Surface通过binder机制与BufferQueue通信。Surface每次向BufferQueue申请一块空的内存,解码结果就保存在这块内存中。之后将装满的内存还给BufferQueue。SurfaceTexture:: updateTexImage的时候,会向BufferQueue请求一块满的内存,并将其内容渲染成GL纹理,随后将空内存还给BufferQueue。可以说Surface与SurfaceTexture形成了一个经典的生产者—消费者模式。

这样HTML5 Video的大体渲染框架就搭建完成了。

 

4.     控制条

播放时,与Video相配套的还有一个控制条,它是完全由webkit来实现的。控制条所涉及的元素都定义在MediaControlElement.h文件中。包括开始按钮,时间条,全屏按钮等元素。这些元素同时也负责接收并处理触摸事件。例如MediaControlTimelineElement对应于时间条,它的defaultEventHandler函数处理时间条相关的事件,比如时间改变,会导致调用seek函数。这个过程在前面已经介绍过了。

上述的所有元素都被添加到MediaControlRootElement上,而MediaControlRootElement又被添加到ShadowRoot上。熟悉前端技术的同学可能对ShadowRoot比较熟悉了,没错,控制条是在一棵Shadow DOM子树中,游离于主DOM树之外的。

控制条并不是一个HTML DOM节点,当然也就没有对应的Render节点。它的绘制在RenderSkinMediaButton类的draw函数中完成。

从该函数中看,控制条中相关元素(如play按钮)对应的是一个RenderBox类,然后根据其RenderStyle中的ControlPart熟悉来确定究竟是什么按钮,应该进行怎样绘制。


5.   全屏模式

控制条全屏按钮可以使视频进入全屏模式,全屏模式的绘制方式与普通模式不太相同,全屏模式专门有一个类HTML5VideoFullScreen来支持,它是HTML5VideoView的子类。

按全屏按钮后流程如下:

#0  WebKit::HTML5VideoViewProxy::enterFullscreenForVideoLayer 
#1  WebKit::HTML5VideoViewProxyDelegate::enterFullscreenForVideoLayer 
#2  WebCore::MediaPlayerVideoPrivate::enterFullscreenMode 
#3  WebCore::MediaPlayerVideoPrivate::enterFullscreenMode 
#4  android::ChromeClientAndroid::enterFullscreenForNode 
#5  android::ChromeClientAndroid::enterFullscreenForNode 
#6  WebCore::HTMLMediaElement::enterFullscreen
#7  WebCore::HTMLMediaElement::enterFullscreen 
#8  WebCore::MediaControlFullscreenButtonElement::defaultEventHandler


#0  VideoSurfaceView 
#1  HTML5VideoFullScreen 
#2  WebKit::VideoPlayer::enterFullScreenVideo 
#3  WebKit::HTML5VideoViewProxy::handleMessage 
#4  gaia::Handler::dispatchMessage 

中间经过了WebCore线程到UI线程的转换,可见在HTML5VideoFullScreen 中创建了一个VideoSurfaceView对象。VideoSurfaceView是一个SurfaceView,在SurfaceView创建时会触发如下事件:

#0  WebKit::HTML5VideoFullScreen::attachMediaController 
#1  WebKit::HTML5VideoFullScreen::setMediaController 
#2  WebKit::HTML5VideoFullScreen::prepareForFullScreen 
#3  WebKit::HTML5VideoFullScreen::VideoSurfaceHolderCallback::surfaceCreated 

在attachMediaController 函数中将VideoSurfaceView设置给MediaController,这样MediaController解析的视频直接输出给VideoSurfaceView来显示(在HTML5VideoFullScreen 是用MediaController来播放视频,MediaController相当于一个带操作条的MediaPlayer),这样就没有了上述普通模式那样复杂的显示过程。全屏模式的显示过程与webkit是没有一毛钱关系的,完全由android ui控件来显示。那么VideoSurfaceView怎样与浏览器的UI联系起来呢? 通过onShowCustomView回调。将控件回调给浏览器UI,与UI联系起来。

1 0