分享一下我老师大神的人工智能教程!零基础,通俗易懂!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