Chromium为视频标签 video 渲染视频画面的过程分析

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

       在浏览器中,<video>标签与普通标签有一个显著不同点,它们的内容不是由浏览器自己绘制出来,而是由第三方组件提供的。例如,在Android平台上,<video>标签的内容来自于系统播放器MediaPlayer的输出。然而在非全屏模式下,<video>标签的内容又需要像普通标签一样,嵌入在HTML页面中显示,也就是由浏览器进行渲染。本文接下来就分析Chromium渲染<video>标签内容的原理。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       浏览器是否能够无缝地渲染播放器的输出,取决于播放器是否有良好的设计。一个有良好设计的播放器要有独立的输入和输出。输入就是一个URL或者一个本地文件路径,输出即为一帧一帧的视频画面。播放器都能接受URL或者本地文件路径作为输入,也就是输入这一点都能满足要求。在输出上,它的设计就很有讲究了,有上中下三种策略。

      下策是让使用者提供一个窗口作为播放器的输出。这显然是不合适的,因为一般来说,播放器的使用者除了要在窗口显示视频内容之外,还需要显示其它内容,也就是需要在窗口上放其它控件。当然,如果系统支持将一个窗口作为一个控件嵌入在另外一个窗口中显示,这种设计也未尝不可,不过这种设计太不通用了。

       中策是让使用者提供一个控件作为播放器的输出。这种方式可以解决下策中提出的问题。然而,有一类特殊的使用者,它们的主UI不是通过系统提供控件设计出来的,而是用自己的方式绘制出来的。例如,在浏览器中,网页中的元素就不是通过系统提供的控件显示出来的,而是用自己的图形渲染引擎绘制出来的。

       上策是让使用者提供一个缓冲区作为播放器的输出。这种输出使得使用者以非常灵活的方式将视频画面显示出来。不过缺点就是使用者要多做一些工作,也就是将缓冲区的内容渲染出来的。

       将播放器的输出设计为缓冲区时,有一个细节,是非常值得注意的。一般来说,播放器的输出最终要显示在屏幕上。现在流行的系统,渲染基本上都是通过GPU进行的。如果我们提供给播放器的缓冲区,是普通的缓冲区,也就是只有CPU才可以访问的缓冲区,那么使用者在使用GPU渲染的情况下,需要将缓冲区内容上传到GPU去。这就相当于是执行一个纹理上传操作。我们知道,纹理上传是一个非常慢的操作,而视频的数据又很大,分辨率通常达到1080p。因此,理想的设计是让播放器将输出写入到GPU缓冲区中去。不过,这需要系统提供支持。

       好消息是Android平台提供了这样的支持。在Android系统上,SurfaceTexture描述的就是GPU缓冲区,并且以纹理的形式进行渲染。SurfaceTexture可以进一步封装在Surface中。Android系统的MediaPlayer提供了一个setSurface接口,参数是一个Surface,用来接收解码输出,也就是视频画面。这意味着Android系统的MediaPlayer支持将解码输出写入在GPU缓冲区中。这是上上策,得益于Android系统本身的良好的设计。

      Chromium正是利用了SurfaceTexture作为MediaPlayer的解码输出,如图1所示:


图1 以SurfaceTexture作为MediaPlayer的解码输出

       从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章可以知道,在Chromium的Content层,一个网页被抽象为三个Tree:CC Layer Tree、CC Pending Layer Tree和CC Active Layer Tree。其中,CC Layer Tree由Render进程中的Main线程管理,CC Pending Layer Tree和CC Active Layer Tree由Render进程中的Compositor线程管理。CC Pending Layer Tree由CC Layer Tree同步得到,CC Active Layer Tree由CC Pending Layer Tree激活得到。

       Chromium为每一个<video>标签在CC Layer Tree创建一个VideoLayer。这个VideoLayer在CC Active Layer Tree中有一个对应的VideoLayerImpl。由于网页的UI最终是通过渲染CC Active Layer Tree得到的,因此Chromium通过VideoLayerImpl接收MediaPayer的解码输出。

       接下来,我们就先分析Chromium为<video>标签在CC Layer Tree和CC Active Layer Tree中创建VideoLayer和VideoLayerImpl的过程,然后再分析MediaPlayer将解码输出交给VideoLayerImpl渲染的过程。

       从前面Chromium为视频标签<video>创建播放器的过程分析一文可以知道,当Browser进程获得要播放的视频的元数据之后,会调用WebMediaPlayerAndroid类的成员函数OnMediaMetadataChanged通知Render进程,如下所示:

void WebMediaPlayerAndroid::OnMediaMetadataChanged(    const base::TimeDelta& duration, int width, int height, bool success) {  ......  if (success)    OnVideoSizeChanged(width, height);  ......}
      这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

      当参数success的值等于true的时候,表示成功获取了要播放的视频的元数据,也就是长、宽和持续时间等数据。在这种情况下,WebMediaPlayerAndroid类的成员函数OnMediaMetadataChanged就会调用另外一个成员函数OnVideoSizeChanged通知要播放的视频大小发生了变化,如下所示:

void WebMediaPlayerAndroid::OnVideoSizeChanged(int width, int height) {  ......  // Lazily allocate compositing layer.  if (!video_weblayer_) {    video_weblayer_.reset(new WebLayerImpl(cc::VideoLayer::Create(this)));    ......  }  ......}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       WebMediaPlayerAndroid类的成员变量video_weblayer_描述的是<video>标签在CC Layer Tree对应的一个Layer。如果这时候这个Layer还没有创建,那么WebMediaPlayerAndroid类的成员函数OnVideoSizeChanged就会进行创建,也就是创建一个VideoLayer对象。这个VideoLayer对象会进一步封装在一个WebLayerImpl对象,并且保存在WebMediaPlayerAndroid类的成员变量video_weblayer_中。关于WebLayerImpl,可以参考前面Chromium网页Layer Tree创建过程分析一文。它主要是用来连接WebKit层的Graphic Layer Tree和Content层的CC Layer Tree。

       接下来,我们主要关注VideoLayer对象的创建过程,也就是VideoLayer类的静态成员函数Create的实现,如下所示:

scoped_refptr<VideoLayer> VideoLayer::Create(VideoFrameProvider* provider) {  return make_scoped_refptr(new VideoLayer(provider));}
       这个函数定义在文件external/chromium_org/cc/layers/video_layer.cc中。

       从前面的调用过程可以知道,参数provider指向的是一个WebMediaPlayerAndroid对象。这个WebMediaPlayerAndroid对象描述的是Render进程提供的播放器接口。VideoLayer类的静态成员函数Create使用这个WebMediaPlayerAndroid对象创建了一个VideoPlayer对象,并且返回给调用者。

       VideoPlayer对象的创建过程,也就是VideoPlayer类的构造函数的实现,如下所示:

VideoLayer::VideoLayer(VideoFrameProvider* provider) : provider_(provider) {  DCHECK(provider_);}
       这个函数定义在文件external/chromium_org/cc/layers/video_layer.cc中。

       VideoPlayer类的构造函数将参数provider指向的一个WebMediaPlayerAndroid对象保存在成员变量provider_,表示当前正在创建的VideoPlayer对象要渲染的内容由它提供。

       从前面Chromium网页Layer Tree同步为Pending Layer Tree的过程分析一文可以知道,当CC Layer Tree同步为CC Pending Layer Tree的时候,CC Layer Tree中的每一个XXXLayer对象都会在CC Pending Layer Tree中有一个对应的XXXLayerImpl对象。对于<video>标签来说,它在CC Layer Tree中对应的是一个VideoLayer对象,这个VideoLayer对象在CC Pending Layer Tree中对应的是一个VideoLayerImpl对象。这个VideoLayerImpl对象是通过调用VideoLayer类的成员函数CreateLayerImpl创建的,如下所示:

scoped_ptr<LayerImpl> VideoLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) {  return VideoLayerImpl::Create(tree_impl, id(), provider_).PassAs<LayerImpl>();}
       这个函数定义在文件external/chromium_org/cc/layers/video_layer.cc中。

       参数tree_impl指向的是一个LayerTreeImpl对象。这个LayerTreeImpl对象描述的就是CC Pending Layer Tree。VideoLayer类的成员函数CreateLayerImpl使用这个LayerTreeImpl对象,以及当前正在处理的VideoLayer对象的成员变量provider_指向的一个WebMediaPlayerAndroid对象,创建一个VideoLayerImpl对象,也就是在CC Pending Layer Tree中为<video>标签创建了一个类型为VideoLayerImpl的Layer。这是通过调用VideoLayerImpl类的静态成员函数Create实现的,如下所示:

scoped_ptr<VideoLayerImpl> VideoLayerImpl::Create(    LayerTreeImpl* tree_impl,    int id,    VideoFrameProvider* provider) {  scoped_ptr<VideoLayerImpl> layer(new VideoLayerImpl(tree_impl, id));  layer->SetProviderClientImpl(VideoFrameProviderClientImpl::Create(provider));  ......  return layer.Pass();}
      这个函数定义在文件external/chromium_org/cc/layers/video_layer_impl.cc中。

      VideoPlayerImpl类的静态成员函数Create首先创建了一个VideoLayerImpl对象,接着又调用VideoFrameProviderClientImpl类的静态成员函数Create创建了一个VideoFrameProviderClientImpl对象,如下所示:

scoped_refptr<VideoFrameProviderClientImpl>    VideoFrameProviderClientImpl::Create(        VideoFrameProvider* provider) {  return make_scoped_refptr(      new VideoFrameProviderClientImpl(provider));}
      这个函数定义在文件external/chromium_org/cc/layers/video_frame_provider_client_impl.cc中。

      VideoFrameProviderClientImpl类的静态成员函数Create使用参数provider指向的一个WebMediaPlayerAndroid对象创建了一个VideoFrameProviderClientImpl对象,如下所示:

VideoFrameProviderClientImpl::VideoFrameProviderClientImpl(    VideoFrameProvider* provider)    : active_video_layer_(NULL), provider_(provider) {  ......  provider_->SetVideoFrameProviderClient(this);  ......}
       这个函数定义在文件external/chromium_org/cc/layers/video_frame_provider_client_impl.cc中。

       VideoFrameProviderClientImpl类的构造函数除了将参数provider指向的WebMediaPlayerAndroid对象保存在成员变量provider_中,还会调用这个WebMediaPlayerAndroid对象的成员函数SetVideoFrameProviderClient,将当前正在创建的VideoFrameProviderClientImpl对象作为它的Client。这个Client将会负责接收播放器的解码输出。

       WebMediaPlayerAndroid类的成员函数SetVideoFrameProviderClient的实现如下所示:

void WebMediaPlayerAndroid::SetVideoFrameProviderClient(    cc::VideoFrameProvider::Client* client) {  ......  video_frame_provider_client_ = client;}
       这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。

       WebMediaPlayerAndroid类的成员函数SetVideoFrameProviderClient主要是将参数client指向的一个VideoFrameProviderClientImpl对象保存在成员变量video_frame_provider_client_中。

       回到前面分析的VideoLayerImpl类的静态成员函数Create中,它创建了一个VideoFrameProviderClientImpl对象之后,接下来会将这个VideoFrameProviderClientImpl对象设置给前面创建的VideoLayerImpl对象。这是通过调用VideoLayerImpl类的成员函数SetProviderClientImpl实现的,如下所示:

void VideoLayerImpl::SetProviderClientImpl(    scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl) {  provider_client_impl_ = provider_client_impl;}
       这个函数定义在文件external/chromium_org/cc/layers/video_layer_impl.cc中。

       Vide

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值