Chromium为视频标签全屏播放的过程分析_第1页
Chromium为视频标签全屏播放的过程分析_第2页
Chromium为视频标签全屏播放的过程分析_第3页
Chromium为视频标签全屏播放的过程分析_第4页
Chromium为视频标签全屏播放的过程分析_第5页
已阅读5页,还剩28页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Chromium为视频标签<video>全屏播放的过程分析在Chromium中,<video>标签有全屏和非全屏两种播放模式。在非全屏模式下,<video>标签播放的视频嵌入在网页中显示,也就是视频画面作为网页的一部分显示。在全屏模式下,我们是看不到网页其它内容的,因此<video>标签播放的视频可以在一个独立的全屏窗口中显示。这两种截然不同的播放模式,导致Chromium使用不同的方式渲染视频画面。本文接下来就详细分析<video>标签全屏播放的过程。 从前面文章中一文可以知道,在Android平台上,<video>标签

2、指定的视频是通过系统提供的MediaPlayer进行播放的。MediaPlayer提供了一个setSurface接口,用来给MediaPlayer设置一个Surface。Surface内部有一个GPU缓冲区队列,以后MediaPlayer会将解码出来的视频画面写入到这个队列中去。 Surface有两种获取方式。第一种方式是通过SurfaceTexture构造一个新的Surface。第二种方式是从SurfaceView内部获得。在非全屏模式下,Chromium就是通过第一种方式构造一个Surface,然后设置给MediaPlayer的。在全屏模式下,Chromium将会直接创建一个全屏的Surf

3、aceView,然后再从这个SurfaceView内部获得一个Surface,并且设置给MediaPlayer。 在Android平台上,SurfaceView的本质是一个窗口。既然是窗口,那么它的UI就是由系统(SurfaceFlinger)合成在屏幕上显示的。它的UI就来源于它内部的Surface描述的GPU缓冲区队列。因此,当MediaPlayer将解码出来的视频画面写入到SurfaceView内部的Surface描述的GPU缓冲区队列去时,SurfaceFlinger就会从该GPU缓冲区队列中将新写入的视频画面提取出来,并且合成在屏幕上显示。关于SurfaceView的更多知识,可以参

4、考前面文章中一文。 Surface描述的GPU缓冲区队列,是一个生产者/消息者模型。在我们这个情景中,生产者便是MediaPlayer。如果Surface是通过SurfaceTexture构造的,那么SurfaceTexture的所有者,也就是Chromium,就是消费者。消费者有责任将视频画面从GPU缓冲区队列中提取出来,并且进行渲染。渲染完成后,再交给SurfaceFlinger合成显示在屏幕中。如果Surface是从SurfaceView内部获取的,那么SurfaceView就是消费者,然后再交给SurfaceFlinger合成显示在屏幕中。 简单来说,在非全屏模式下,<video

5、>标签的视频画面经过MediaPlayer->Chromium->SurfaceFlinger显示在屏幕中,而在全屏模式下,经过MediaPlayer->SurfaceView->SurfaceFlinger显示在屏幕中。 Chromium支持<video>标签在全屏和非全屏模式之间无缝切换,也就是从一个模式切换到另外一个模式的时候,不需要重新创建MediaPlayer,只需要给原先使用的MediaPlayer设置一个新的Surface即可。图1描述的是<video>标签从非全屏模式切换为全屏模式的示意图,如下所示: 当<video&

6、gt;标签从非全屏模式切换为全屏模式时,Chromium会为它创建一个全屏的SurfaceView,并且将这个SurfaceView内部的Surface设置给MediaPlayer。以后MediaPlayer就不会再将解码出来的视频画面通过原先设置的SurfaceTexture交给Chromium处理,而是通过后面设置的Surface交给SurfaceView处理。 接下来,我们就结合源代码,从<video>标签进入全屏模式开始,分析<video>标签全屏播放视频的过程。从前面文章中一文可以知道,在WebKit中,<video>标签是通过HTMLMediaE

7、lement类描述的。当<video>标签进入全屏模式时,HTMLMediaElement类的成员函数enterFullscreen就会被调用,它的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void HTMLMediaElement:enterFullscreen() WTF_LOG(Media, "HTMLMediaElement:enterFullscreen"); FullscreenElementStack:from(document().requestFullScreenForElement(this,

8、 0, FullscreenElementStack:ExemptIFrameAllowFullScreenRequirement); 这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/html/HTMLMediaElement.cpp中。 在WebKit中,网页的每一个标签都可以进入全屏模式。每一个网页都对应有一个FullscreenElementStack对象。这个FullscreenElementStack对象内部有一个栈,用来记录它对应的网页有哪些标签进入了全屏模式。 HTMLMediaElement类的成员函数

9、enterFullscreen首先调用成员函数document获得当前正在处理的<video>标签所属的网页,然后再通过调用FullscreenElementStack类的静态成员函数from获得这个网页所对应的FullscreenElementStack对象。有了这个FullscreenElementStack对象之后,就可以调用它的成员函数requestFullScreenForElement请求将当前正在处理的<video>标签设置为全屏模式。 FullscreenElementStack类的成员函数requestFullScreenForElement的实现如下

10、所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void FullscreenElementStack:requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType) . / The Mozilla Full Screen API </Gecko:FullScreenAPI> has different requirements / for full screen mo

11、de, and do not have the concept of a full screen element stack. bool inLegacyMozillaMode = (flags & Element:LEGACY_MOZILLA_REQUEST); do . / 1. If any of the following conditions are true, terminate these steps and queue a task to fire / an event named fullscreenerror with its bubbles attribute s

12、et to true on the context object's / node document: . / The context object's node document fullscreen element stack is not empty and its top element / is not an ancestor of the context object. (NOTE: Ignore this requirement if the request was / made via the legacy Mozilla-style API.) if (!m_

13、fullScreenElementStack.isEmpty() && !inLegacyMozillaMode) Element* lastElementOnStack = m_fullScreenElementStack.last().get(); if (lastElementOnStack = element | !lastElementOnStack->contains(element) break; / A descendant browsing context's document has a non-empty fullscreen element

14、 stack. bool descendentHasNonEmptyStack = false; for (Frame* descendant = document()->frame() ? document()->frame()->tree().traverseNext() : 0; descendant; descendant = descendant->tree().traverseNext() . if (fullscreenElementFrom(*toLocalFrame(descendant)->document() descendentHasNon

15、EmptyStack = true; break; if (descendentHasNonEmptyStack && !inLegacyMozillaMode) break; . / 2. Let doc be element's node document. (i.e. "this") Document* currentDoc = document(); / 3. Let docs be all doc's ancestor browsing context's documents (if any) and doc. Deque&

16、lt;Document*> docs; do docs.prepend(currentDoc); currentDoc = currentDoc->ownerElement() ? ¤tDoc->ownerElement()->document() : 0; while (currentDoc); / 4. For each document in docs, run these substeps: Deque<Document*>:iterator current = docs.begin(), following = docs.begin();

17、 do +following; / 1. Let following document be the document after document in docs, or null if there is no / such document. Document* currentDoc = *current; Document* followingDoc = following != docs.end() ? *following : 0; / 2. If following document is null, push context object on document's fu

18、llscreen element / stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute / set to true on the document. if (!followingDoc) from(*currentDoc).pushFullscreenElementStack(element); addDocumentToFullScreenChangeEventQueue(currentDoc); continue; / 3. Otherwise, if doc

19、ument's fullscreen element stack is either empty or its top element / is not following document's browsing context container, Element* topElement = fullscreenElementFrom(*currentDoc); if (!topElement | topElement != followingDoc->ownerElement() / .push following document's browsing co

20、ntext container on document's fullscreen element / stack, and queue a task to fire an event named fullscreenchange with its bubbles attribute / set to true on document. from(*currentDoc).pushFullscreenElementStack(followingDoc->ownerElement(); addDocumentToFullScreenChangeEventQueue(currentDo

21、c); continue; / 4. Otherwise, do nothing for this document. It stays the same. while (+current != docs.end(); / 5. Return, and run the remaining steps asynchronously. / 6. Optionally, perform some animation. . document()->frameHost()->chrome().client().enterFullScreenForElement(element); / 7.

22、Optionally, display a message indicating how the user can exit displaying the context object fullscreen. return; while (0); . 这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/dom/FullscreenElementStack.cpp中。 FullscreenElementStack类的成员函数requestFullScreenForElement主要是用来为网页中的每一个Document建立一

23、个Stack。这个Stack记录了Document中所有请求设置为全屏模式的标签。我们通过图2所示的例子说明FullscreenElementStack类的成员函数requestFullScreenForElement的实现:图2所示的网页包含了两个Document:Doc1和Doc2。其中,Doc1通过<iframe>标签加载了Doc2,后者里面有一个<video>标签。当Doc2里面的<video>标签被设置为全屏模式时,Doc1的Stack会被压入一个<iframe>标签和一个<video>标签,其中,<iframe>

24、;标签代表的是Doc1,Doc2的Stack会被压入一个<video>标签。 注释中提到,Mozilla定义的Fullscreen API没有Fullscreen Element Stack的概念。没有Fullscreen Element Stack,意味着网页的标签在任意情况下都可以设置为全屏模式。不过,非Mozilla定义的Fullscreen API是要求Fullscreen Element Stack的。Fullscreen Element Stack用来限制一个标签是否可以设置为全屏模式: 1. 当Stack为空时,任意标签均可设置为全屏模式。 2. 当Stack非空时,

25、栈顶标签的子标签才可以设置为全屏模式。 FullscreenElementStack类的成员函数requestFullScreenForElement就是根据上述逻辑判断参数element描述的标签是否可以设置为全屏模式的。如果可以,那么就会更新与它相关的Stack,并且在最后调用在WebKit Glue层创建一个WebViewImpl对象的成员函数enterFullScreenForElement,用来通知WebKit Glue层有一个标签要进入全屏模式。这个WebViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文。WebKit Glue层的作

26、用,可以参考前面文章中一文。 接下来,我们就继续分析WebViewImpl类的成员函数enterFullScreenForElement的实现,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void WebViewImpl:enterFullScreenForElement(WebCore:Element* element) m_fullscreenController->enterFullScreenForElement(element); 这个函数定义在文件external/chromium_org/third_party/WebKit/So

27、urce/web/WebViewImpl.cpp中。 WebViewImpl类的成员变量m_fullscreenController指向的是一个FullscreenController对象。WebViewImpl类的成员函数enterFullScreenForElement调用这个FullscreenController对象的成员函数enterFullScreenForElement,用来通知它将参数element描述的标签设置为全屏模式。 FullscreenController类的成员函数enterFullScreenForElement的实现如下所示:cpp view plain cop

28、y 在CODE上查看代码片派生到我的代码片void FullscreenController:enterFullScreenForElement(WebCore:Element* element) / We are already transitioning to fullscreen for a different element. if (m_provisionalFullScreenElement) m_provisionalFullScreenElement = element; return; / We are already in fullscreen mode. if (m_fu

29、llScreenFrame) m_provisionalFullScreenElement = element; willEnterFullScreen(); didEnterFullScreen(); return; / We need to transition to fullscreen mode. if (WebViewClient* client = m_webViewImpl->client() if (client->enterFullScreen() m_provisionalFullScreenElement = element; 这个函数定义在文件externa

30、l/chromium_org/third_party/WebKit/Source/web/FullscreenController.cpp中。 如果参数element描述的标签所在的网页已经有标签处于全屏模式,那么FullscreenController类的成员变量m_provisionalFullScreenElement就会指向该标签,并且FullscreenController类的另外一个成员变量m_fullScreenFrame会指向一个LocalFrame对象。这个LocalFrame对象描述的就是处于全屏模式的标签所在的网页。 我们假设参数element描述的标签所在的网页还没有标

31、签被设置为全屏模式。这时候FullscreenController类的成员函数enterFullScreenForElement会调用成员变量m_webViewImpl指向的一个WebViewImpl对象的成员函数client获得一个WebViewClient接口,然后再调用这个WebViewClient接口的成员函数enterFullScreen,用来通知它进入全屏模式。 上述WebViewClient接口是由WebKit的使用者设置的。在我们这个情景中,WebKit的使用者即Render进程中的Content模块,它设置的WebViewClient接口指向的是一个RenderViewImp

32、l对象。这个RenderViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文,它描述的是当前正在处理的网页所加载在的一个RenderView控件。这一步实际上是通知Chromium的Content层进入全屏模式。 FullscreenController类的成员函数enterFullScreenForElement通知了Content层进入全屏模式之后,会将引发Content层进入全屏模式的标签记录在成员变量m_provisionalFullScreenElement中。在我们这个情景中,这个标签即为一个<video>标签。 接下来我们

33、继续分析Chromium的Content层进入全屏模式的过程,也就是RenderViewImpl类的成员函数enterFullScreen的实现,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片bool RenderViewImpl:enterFullScreen() Send(new ViewHostMsg_ToggleFullscreen(routing_id_, true); return true; 这个函数定义在文件external/chromium_org/content/renderer/render_view_impl.cc。 Rende

34、rViewImpl类的成员函数enterFullScreen向Browser进程发送一个类型为ViewHostMsg_ToggleFullscreen的IPC消息,用来通知它将Routing ID为routing_id_的网页所加载在的Tab设置为全屏模式。 Browser进程通过RenderViewHostImpl类的成员函数OnMessageReceived接收类型为ViewHostMsg_ToggleFullscreen的IPC消息,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片bool RenderViewHostImpl:OnMessageR

35、eceived(const IPC:Message& msg) . bool handled = true; IPC_BEGIN_MESSAGE_MAP(RenderViewHostImpl, msg) . IPC_MESSAGE_HANDLER(ViewHostMsg_ToggleFullscreen, OnToggleFullscreen) . IPC_MESSAGE_UNHANDLED( handled = RenderWidgetHostImpl:OnMessageReceived(msg) IPC_END_MESSAGE_MAP() return handled; 这个函数定

36、义在文件external/chromium_org/content/browser/renderer_host/render_view_host_impl.cc中。 RenderViewHostImpl类的成员函数OnMessageReceived将类型为ViewHostMsg_ToggleFullscreen的IPC消息分发给另外一个成员函数OnToggleFullscreen处理,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void RenderViewHostImpl:OnToggleFullscreen(bool enter_fullscre

37、en) DCHECK_CURRENTLY_ON(BrowserThread:UI); delegate_->ToggleFullscreenMode(enter_fullscreen); / We need to notify the contents that its fullscreen state has changed. This / is done as part of the resize message. WasResized(); 这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_vie

38、w_host_impl.cc中。 RenderViewHostImpl类的成员函数OnToggleFullscreen首先通过成员变量delegate_描述的一个RenderViewHostDelegate委托接口将浏览器窗口设置为全屏模式,然后再调用另外一个成员函数WasResized通知Render进程,浏览器窗口大小已经发生了变化,也就是它进入了全屏模式。 RenderViewHostImpl类的成员变量delegate_描述的RenderViewHostDelegate委托接口指向的是一个WebContentsImpl对象。这个WebContentsImpl对象是Content层提供给

39、外界的一个接口,用来描述当前正在加载的一个网页。外界通过这个接口就可以访问当前正在加载的网页。 RenderViewHostImpl类的成员函数WasResized是从父类RenderWidgetHostImpl继承下来的,它的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void RenderWidgetHostImpl:WasResized() . is_fullscreen_ = IsFullscreen(); . ViewMsg_Resize_Params params; . params.is_fullscreen = is_fullsc

40、reen_; if (!Send(new ViewMsg_Resize(routing_id_, params) resize_ack_pending_ = false; else last_requested_size_ = new_size; 这个函数定义在文件external/chromium_org/content/browser/renderer_host/render_widget_host_impl.cc中。 RenderWidgetHostImpl类的成员函数WasResized主要是向Render进程发送一个类型为ViewMsg_Resize的IPC消息。这个ViewMsg_

41、Resize的IPC消息。携带了一个is_fullscreen信息,用来告知Render进程当前正在处理的网页是否已经进入了全屏模式。 Render进程通过RenderWidget类的成员函数OnMessageReceived接收类型为ViewMsg_Resize的IPC消息,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片bool RenderWidget:OnMessageReceived(const IPC:Message& message) bool handled = true; IPC_BEGIN_MESSAGE_MAP(Render

42、Widget, message) . IPC_MESSAGE_HANDLER(ViewMsg_Resize, OnResize) . IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; 这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。 RenderWidget类的成员函数OnMessageReceived将类型为ViewMsg_Resize的IPC消息分发给另外一个成员函数OnResize处理,如下所示:cpp

43、 view plain copy 在CODE上查看代码片派生到我的代码片void RenderWidget:OnResize(const ViewMsg_Resize_Params& params) . Resize(params.new_size, params.physical_backing_size, params.overdraw_bottom_height, params.visible_viewport_size, params.resizer_rect, params.is_fullscreen, SEND_RESIZE_ACK); . 这个函数定义在文件externa

44、l/chromium_org/content/renderer/render_widget.cc中。 RenderWidget类的成员函数OnResize又调用另外一个成员函数Resize处理类型为ViewMsg_Resize的IPC消息,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void RenderWidget:Resize(const gfx:Size& new_size, const gfx:Size& physical_backing_size, float overdraw_bottom_height, const gf

45、x:Size& visible_viewport_size, const gfx:Rect& resizer_rect, bool is_fullscreen, ResizeAck resize_ack) . if (compositor_) compositor_->setViewportSize(new_size, physical_backing_size); . . / NOTE: We may have entered fullscreen mode without changing our size. bool fullscreen_change = is_f

46、ullscreen_ != is_fullscreen; if (fullscreen_change) WillToggleFullscreen(); is_fullscreen_ = is_fullscreen; if (size_ != new_size) size_ = new_size; / When resizing, we want to wait to paint before ACK'ing the resize. This / ensures that we only resize as fast as we can paint. We only need to /

47、send an ACK if we are resized to a non-empty rect. webwidget_->resize(new_size); . if (fullscreen_change) DidToggleFullscreen(); . 这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。 RenderWidget类的成员函数Resize一方面会通知网页的UI合成器,它负责渲染的网页的大小发生了变化,以便它修改网页UI的Viewport大小。这是通过调用成员变量compositor_指

48、向的一个RenderWidgetCompositor对象的成员函数setViewportSize实现的。这个RenderWidgetCompositor对象的创建过程可以参考前面文章中一文。 另一方面,RenderWidget类的成员函数Resize又会通知WebKit,它当前正在加载网页的大小发生了变化。这是通过调用成员变量webwidget_指向的一个WebViewImpl对象的成员函数resize实现的。这个WebViewImpl对象的创建过程可以参考前面Chromium网页Frame Tree创建过程分析一文。 此外,RenderWidget类的成员函数Resize还会判断网页是否从全

49、屏模式退出,或者进入全屏模式。如果是的话,那么就在通知WebKit修改网页的大小前后,RenderWidget类的成员函数Resize还会分别调用另外两个成员函数WillToggleFullscreen和DidToggleFullscreen,用来通知WebKit网页进入或者退出了全屏模式。 在我们这个情景中,网页是进入了全屏模式。接下来我们就先分析RenderWidget类的成员函数WillToggleFullscreen的实现,然后再分析另外一个成员函数DidToggleFullscreen的实现。 RenderWidget类的成员函数WillToggleFullscreen的实现如下所示

50、:cpp view plain copy 在CODE上查看代码片派生到我的代码片void RenderWidget:WillToggleFullscreen() . if (is_fullscreen_) webwidget_->willExitFullScreen(); else webwidget_->willEnterFullScreen(); 这个函数定义在文件external/chromium_org/content/renderer/render_widget.cc中。 从前面的分析可以知道,RenderWidget类的成员函数WillToggleFullscreen是

51、在通知WebKit进入全屏模式之前被调用的。这时候RenderWidget类的成员变量is_fullscreen_的值为false。因此,接下来RenderWidget类的成员函数WillToggleFullscreen会调用成员变量webwidget_指向的一个WebViewImpl对象的成员函数willEnterFullScreen,用来通知WebKit它即将要进入全屏模式。 WebViewImpl类的成员函数willEnterFullScreen的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void WebViewImpl:willEnterFullScreen() m_fullscreenController->willEnterFullScreen(); 这个函数定义在文件external/chromium_org/th

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论