在浏览器中,<video>标签与普通标签有一个显著不同点,它们的内容不是由浏览器自己绘制出来,而是由第三方组件提供的。例如,在Android平台上,<video>标签的内容来自于系统播放器MediaPlayer的输出。然而在非全屏模式下,<video>标签的内容又需要像普通标签一样,嵌入在HTML页面中显示,也就是由浏览器进行渲染。本文接下来就分析Chromium渲染<video>标签内容的原理。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
浏览器是否能哆无缝地渲染播放器的输出,取决于播放器是否有良好的设计。一个有良好设计的播放器要有独立的输入和输出。输入就是一个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中。
VideoLayerImpl类的成员函数SetProviderClientImpl将参数provider_client_impl指向的一个VideoFrameProviderClientImpl对象保存在成员变量provider_client_impl_中。
这一步执行完成之后,Chromium就为<video>标签在CC Pending Layer Tree中创建了一个VideoLayerImpl对象。从前面Chromium网页Pending Layer Tree激活为Active Layer Tree的过程分析一文可以知道,这个VideoLayerImpl对象在CC Pending Layer Tree激活为CC Active Layer Tree的时候,会变成CC Active Layer Tree中的一个节点。
这样,Chromium就为<video>标签在CC Active Layer Tree中创建了一个类型为VideoLayerImpl的Layer。接下来我们继续分析Chromium创建SurfaceTexture接收MediaPlayer的解码输出的过程。
从前面Chromium为视频标签<video>创建播放器的过程分析一文可以知道,获得了<video>标签要播放的视频的元数据之后,WebKit层就会请求Content层启动它的播放器对视频进行播放,也就是调用WebMediaPlayerAndroid类的成员函数play对视频进行播放,它的实现如下所示:
void WebMediaPlayerAndroid::play() { ...... TryCreateStreamTextureProxyIfNeeded(); ...... if (hasVideo() && needs_establish_peer_ && !player_manager_->IsInFullscreen(frame_)) { EstablishSurfaceTexturePeer(); } if (paused()) player_manager_->Start(player_id_); ...... }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员函数play的详细分析可以参考前面Chromium为视频标签<video>创建播放器的过程分析一文。这里我们主要它关注它调用的另外两个成员函数TryCreateStreamTextureProxyIfNeeded和EstablishSurfaceTexturePeer的实现。其中,前者用来创建一个SurfaceTexture,后者用来将创建出来的SurfaceTexture封装成一个Surface,并且设置为MediaPlayer的解码输出。
WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded的实现如下所示:
void WebMediaPlayerAndroid::TryCreateStreamTextureProxyIfNeeded() { // Already created. if (stream_texture_proxy_) return; ...... stream_texture_proxy_.reset(stream_texture_factory_->CreateProxy()); if (stream_texture_proxy_) { DoCreateStreamTexture(); ReallocateVideoFrame(); if (video_frame_provider_client_) { stream_texture_proxy_->BindToLoop( stream_id_, video_frame_provider_client_, compositor_loop_); } } }
这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员变量stream_texture_proxy_指向的是一个StreamTextureProxyImpl对象。这个StreamTextureProxyImpl对象用来在Compositor线程中接收MediaPlayer的解码输出。
WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded首先检查员变量stream_texture_proxy_是否已经指向了一个StreamTextureProxyImpl对象。如果已经指向,那么就说明Chromium已经为当前正在处理的WebMediaPlayerAndroid创建过了一个SurfaceTexture。在这种情况下,WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded就什么也不做就返回。
另一方面,如果WebMediaPlayerAndroid类的成员变量stream_texture_proxy_还没有指向一个StreamTextureProxyImpl对象,那么WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded就会调用另外一个成员变量stream_texture_factory_指向的一个StreamTextureFactoryImpl对象的成员函数CreateProxy创建一个StreamTextureProxyImpl对象,并且保存在成员变量stream_texture_proxy_中。
WebMediaPlayerAndroid类的成员变量stream_texture_factory_指向的StreamTextureFactoryImpl对象的创建过程可以参考前面Chromium为视频标签<video>创建播放器的过程分析一文,它内部包含有一个GpuChannelHost对象。这个GpuChannelHost对象描述的是一个连接到GPU进程的GPU通道。
StreamTextureFactoryImpl类的成员函数CreateProxy的实现如下所示:
StreamTextureProxy* StreamTextureFactoryImpl::CreateProxy() { DCHECK(channel_.get()); StreamTextureHost* host = new StreamTextureHost(channel_.get()); return new StreamTextureProxyImpl(host); }
这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。
StreamTextureFactoryImpl类的成员变量channel_描述的就是上述的GPU通道。StreamTextureFactoryImpl类的成员函数CreateProxy首先使用这个GPU通道创建一个StreamTextureHost对象,然后再将这个StreamTextureHost对象封装在一个StreamTextureProxyImpl对象中返回给调用者。这个StreamTextureHost对象描述的实际上就是Render进程接下来要求GPU进程创建的SurfaceTexture。
回到WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded中,它创建了一个StreamTextureProxyImpl对象之后,紧接着又会调用另外一个成员函数DoCreateStreamTexture请求GPU进程创建一个SurfaceTexture,如下所示:
void WebMediaPlayerAndroid::DoCreateStreamTexture() { ...... stream_id_ = stream_texture_factory_->CreateStreamTexture( kGLTextureExternalOES, &texture_id_, &texture_mailbox_); }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员函数DoCreateStreamTexture调用成员变量stream_texture_factory_指向的StreamTextureFactoryImpl对象的成员函数CreateStreamTexture请求GPU进程创建一个SurfaceTexture对象,如下所示:
unsigned StreamTextureFactoryImpl::CreateStreamTexture( unsigned texture_target, unsigned* texture_id, gpu::Mailbox* texture_mailbox) { GLuint stream_id = 0; gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); gl->GenTextures(1, texture_id); stream_id = gl->CreateStreamTextureCHROMIUM(*texture_id); gl->GenMailboxCHROMIUM(texture_mailbox->name); gl->BindTexture(texture_target, *texture_id); gl->ProduceTextureCHROMIUM(texture_target, texture_mailbox->name); return stream_id; }这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。
在Android中,创建一个SurfaceTexture对象,需要指定一个纹理ID。因此,StreamTextureFactoryImpl类的成员函数CreateStreamTexture在请求GPU进程创建SurfaceTexture对象之前,首先会创建一个纹理。这个纹理可以通过调用Chromium为Render进程提供的Command Buffer OpenGL接口的成员函数GenTextures创建。
StreamTextureFactoryImpl类的成员变量context_provider_指向的是一个ContextProviderCommandBuffer对象。调用这个ContextProviderCommandBuffer对象的成员函数ContextGL可以获得一个Command Buffer GL接口。当我们调用这个Command Buffer GL接口的成员函数执行GPU命令时,它实际上是通过Command Buffer将GPU命令发送给GPU进程执行。关于Command Buffer GL的更多知识,可以参考前面Chromium硬件加速渲染机制基础知识简要介绍和学习计划这个系列的文章。
获得了一个纹理ID之后,StreamTextureFactoryImpl类的成员函数CreateStreamTexture就继续调用上述Command Buffer GL接口的成员函数CreateStreamTextureCHROMIUM请求创建GPU进程创建一个SurfaceTexutre对象。
创建了SurfaceTexutre对象之后,StreamTextureFactoryImpl类的成员函数CreateStreamTexture还会为前面创建出来的纹理创建一个Mailbox。这个Mailbox的作用是将与它关联的纹理发送给Browser进程进行合成,以便显示在浏览器窗口中。这个纹理描述的实际上就是MediaPlayer的解码输出,因此,上述Mailbox是用来将MediaPlayer的解码输出交给Browser进程合成显示在浏览器窗口中。关于Mailbox的更多知识,可以参考前面Chromium硬件加速渲染的UI合成过程分析一文。
接下来我们继续分析Render进程请求GPU进程创建SurfaceTexture对象的过程,也就是Command Buffer GL接口的成员函数CreateStreamTextureCHROMIUM的实现。
从前面Chromium硬件加速渲染机制基础知识简要介绍和学习计划这个系列的文章可以知道,Chromium是通过GLES2Implementation类来描述Command Buffer GL接口的,因此接下来我们分析GLES2Implementation类的成员函数CreateStreamTextureCHROMIUM的实现,如下所示:
GLuint GLES2Implementation::CreateStreamTextureCHROMIUM(GLuint texture) { ...... return gpu_control_->CreateStreamTexture(texture); }这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。
从前面Chromium硬件加速渲染的OpenGL上下文创建过程分析一文可以知道,GLES2Implementation类的成员变量gpu_control_指向的是一个CommandBufferProxyImpl对象。GLES2Implementation类的成员函数CreateStreamTextureCHROMIUM调用这个CommandBufferProxyImpl对象的成员函数CreateStreamTexture请求GPU进程创建一个SurfaceTexture对象,如下所示:
uint32 CommandBufferProxyImpl::CreateStreamTexture(uint32 texture_id) { ...... int32 stream_id = channel_->GenerateRouteID(); bool succeeded; Send(new GpuCommandBufferMsg_CreateStreamTexture( route_id_, texture_id, stream_id, &succeeded)); ...... return stream_id; }这个函数定义在文件external/chromium_org/content/common/gpu/client/command_buffer_proxy_impl.cc中。
CommandBufferProxyImpl类的成员函数CreateStreamTexture向GPU进程发送一个类型为GpuCommandBufferMsg_CreateStreamTexture的IPC消息,用来请求创建一个SurfaceTexture对象。
GPU进程通过GpuCommandBufferStub类的成员函数OnMessageReceived接收类型为GpuCommandBufferMsg_CreateStreamTexture的IPC消息,如下所示:
bool GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) { ...... bool handled = true; IPC_BEGIN_MESSAGE_MAP(GpuCommandBufferStub, message) ...... IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_CreateStreamTexture, OnCreateStreamTexture) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() ..... return handled; }这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。
GpuCommandBufferStub类的成员函数OnMessageReceived将类型为GpuCommandBufferMsg_CreateStreamTexture的IPC消息分发给另外一个成员函数OnCreateStreamTexture处理,如下所示:
void GpuCommandBufferStub::OnCreateStreamTexture( uint32 texture_id, int32 stream_id, bool* succeeded) { #if defined(OS_ANDROID) *succeeded = StreamTexture::Create(this, texture_id, stream_id); #else *succeeded = false; #endif }这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。
SurfaceTexture是Android平台特有的接口,因此GpuCommandBufferStub类的成员函数OnCreateStreamTexture只有在Android平台上才会响应请求创建一个SurfaceTexture对象。
这个SurfaceTexture对象是通过调用StreamTexture类的静态成员函数Create创建的,如下所示:
bool StreamTexture::Create( GpuCommandBufferStub* owner_stub, uint32 client_texture_id, int stream_id) { GLES2Decoder* decoder = owner_stub->decoder(); TextureManager* texture_manager = decoder->GetContextGroup()->texture_manager(); TextureRef* texture = texture_manager->GetTexture(client_texture_id); if (texture && (!texture->texture()->target() || texture->texture()->target() == GL_TEXTURE_EXTERNAL_OES)) { // TODO: Ideally a valid image id was returned to the client so that // it could then call glBindTexImage2D() for doing the following. scoped_refptr<gfx::GLImage> gl_image( new StreamTexture(owner_stub, stream_id, texture->service_id())); gfx::Size size = gl_image->GetSize(); texture_manager->SetTarget(texture, GL_TEXTURE_EXTERNAL_OES); texture_manager->SetLevelInfo(texture, GL_TEXTURE_EXTERNAL_OES, 0, GL_RGBA, size.width(), size.height(), 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, true); texture_manager->SetLevelImage( texture, GL_TEXTURE_EXTERNAL_OES, 0, gl_image); return true; } return false; }这个函数定义在文件external/chromium_org/content/common/gpu/stream_texture_android.cc中。
参数client_texture_id描述的是一个纹理ID,不过这个纹理ID是在Render进程中分配的,称为Client ID。在Chromium中,所有的OpenGL相关的对象都是需要在GPU进程创建的。GPU进程在创建这些对象的时候,会获得一个相应的ID,称为Service ID。Client ID和Service ID是一一对应的。
例如,Render进程需要一个纹理对象时。它就会先在本进程获得一个Client ID,然后该Client ID发送给GPU进程,让GPU进程调用OpenGL函数为其创建一个纹理对象。这个纹理对象与指定的Client ID关联,并且被封装在一个TextureRef对象中,由GPU进程中的一个TextureManager对象进行管理。调用这个TextureRef对象的成员函数service_id可以获得一个Service ID。这个Service ID实际上就是调用OpenGL函数glGenTextures时获得的纹理ID。
StreamTexture类的静态成员函数Create在为参数client_texture_id创建SurfaceTexture之前,首先要确保它已经与一个TextureRef对象关联,也就是GPU进程已经为它创建过纹理对象。此外,StreamTexture类的静态成员函数Create还需要确保这个纹理对象的类型为GL_TEXTURE_EXTERNAL_OES,而不是GL_TEXTURE。类型为GL_TEXTURE的纹理是OpenGL提供的标准纹理,而类型为GL_TEXTURE_EXTERNAL_OES的纹理是由厂商扩展的,它有特殊的用途。在Android平台上,它的特殊用途就是用来创建SurfaceTexture。
一旦上述条件得到满足了,StreamTexture类的静态成员函数Create就会创建一个StreamTexture对象,并且将这个StreamTexture对象交给对应的TextureManager对象管理。这个StreamTexture对象在创建的过程中,同时会创建一个SurfaceTexture对象,如下所示:
StreamTexture::StreamTexture(GpuCommandBufferStub* owner_stub, int32 route_id, uint32 texture_id) : surface_texture_(gfx::SurfaceTexture::Create(texture_id)), ...... { ...... surface_texture_->SetFrameAvailableCallback(base::Bind( &StreamTexture::OnFrameAvailable, weak_factory_.GetWeakPtr())); }这个函数定义在文件external/chromium_org/content/common/gpu/stream_texture_android.cc中。
StreamTexture类的构造函数通过调用SurfaceTexture类的静态成员函数Create创建一个SurfaceTexture对象,并且保存在成员变量surface_texture_中。SurfaceTexture类的静态成员函数Create的实现可以参考前面Chromium网页CPU光栅化原理分析一文。它实际上是通过JNI在Java层创建了一个由Android SDK提供的SurfaceTexture对象。这个Java层的SurfaceTexture对象会被封装在C++层中的一个SurfaceTexture对象中。这个C++层的SurfaceTexture对象就保存在StreamTexture类的成员变量surface_texture_中。
StreamTexture类的构造函数最后还做的一件事情是调用前面创建的C++层的SurfaceTexture对象的成员函数SetFrameAvailableCallback,用来设置一个Frame Available Callback。这个Frame Available Callback绑定了StreamTexture类的成员函数OnFrameAvailable。这意味着当上述设置的Frame Available Callback被执行时,StreamTexture类的成员函数OnFrameAvailable就会被调用。
C++层的SurfaceTexture类的成员函数SetFrameAvailableCallback又会通过JNI调用Java层的SurfaceTexture类的成员函数setOnFrameAvailableListener给前面在Java层创建的SurfaceTexture对象设置一个Frame Available Listener。一个SurfaceTexture对象描述的实际上是一个GPU缓冲区队列。这是一个生产者/消费者队列。在我们这个情景中,生产者即为Android系统提供的MediaPlayer,消费者即为上述在Java层设置的Frame Available Listener。
每当MediaPlayer解码出一帧视频之后,它都会从设置给它的SurfaceTexture对象中获得一个GPU缓冲区,然后将解码出来的视频帧数据拷贝到该GPU缓冲区中去,并且通知上述在Java层设置的Frame Available Listener,有新的GPU缓冲区可用。Java层的Frame Available Listener又会进一步通过JNI执行上述在C++层设置的Frame Available Callback。这时候StreamTexture类的成员函数OnFrameAvailable就会被调用。
StreamTexture类的成员函数OnFrameAvailable的调用过程我们后面再分析。现在回到前面分析的WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded中,这时候它就请求GPU进程创建了一个SurfaceTexture对象。
WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded接下来调用成员函数ReallocateVideoFrame将前面用来创建SurfaceTexture的纹理封装在一个VideoFrame对象中,如下所示:
void WebMediaPlayerAndroid::ReallocateVideoFrame() { if (needs_external_surface_) { ...... } else if (!is_remote_ && texture_id_) { ...... scoped_refptr<VideoFrame> new_frame = VideoFrame::WrapNativeTexture( make_scoped_ptr(new gpu::MailboxHolder( texture_mailbox_, texture_target, texture_mailbox_sync_point)), media::BindToCurrentLoop(base::Bind( &OnReleaseTexture, stream_texture_factory_, texture_id_ref)), natural_size_, gfx::Rect(natural_size_), natural_size_, base::TimeDelta(), VideoFrame::ReadPixelsCB()); SetCurrentFrameInternal(new_frame); } }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
当Chromium用来实现WebView时,WebMediaPlayerAndroid类的成员变量needs_external_surface_的值会等于true。我们不考虑这一种情况。
当WebMediaPlayerAndroid类的成员变量is_remote_的值等于true时,表示<video>标签的视频在一个远程设备上播放,也就是不在本设备上播放。我们也不考虑这一种情况。
从前面的调用过程可以知道,WebMediaPlayerAndroid类的成员变量texture_id_描述的是一个纹理ID。这个纹理ID就是前面用来创建SurfaceTexture对象所使用的纹理ID。这个纹理ID同时封装在一个Mailbox中。这个Mailbox由WebMediaPlayerAndroid类的另外一个成员变量texture_mailbox_中。
排除上述的两种情况之后,如果WebMediaPlayerAndroid类的成员变量texture_id_的值不等于0,也就是前面我们成功创建了一个SurfaceTexture对象,那么WebMediaPlayerAndroid类的成员函数ReallocateVideoFrame就会创建一个VideoFrame对象。这个VideoFrame对象封装了WebMediaPlayerAndroid类的成员变量texture_mailbox_所描述的一个Mailbox。这个Mailbox同时又与前面用来创建SurfaceTexture对象的纹理关联。因此,以后通过这里创建出来的VideoFrame对象将可以访问到前面创建出来的SurfaceTexture对象的内容,也就是MediaPlayer的解码输出。
WebMediaPlayerAndroid类的成员函数ReallocateVideoFrame最后调用另外一个成员函数SetCurrentFrameInternal将前面创建出来的VideoFrame保存在内部,如下所示:
void WebMediaPlayerAndroid::SetCurrentFrameInternal( scoped_refptr<media::VideoFrame>& video_frame) { base::AutoLock auto_lock(current_frame_lock_); current_frame_ = video_frame; }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员函数SetCurrentFrameInternal将参数video_frame描述的VideoFrame对象保存在成员变量current_frame_中。后面我们将会看到这个VideoFrame对象是如何使用的。
这一步执行完成后,再回到WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded中,它最后还要做一件事情,就是指定一个对象处理MediaPlayer的解码输出,这个对象就是它的成员变量video_frame_provider_client_所指向的VideoFrameProviderClientImpl对象。
为了方便描述,我们重新列出WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded的实现,如下所示:
void WebMediaPlayerAndroid::TryCreateStreamTextureProxyIfNeeded() { // Already created. if (stream_texture_proxy_) return; ...... stream_texture_proxy_.reset(stream_texture_factory_->CreateProxy()); if (stream_texture_proxy_) { DoCreateStreamTexture(); ReallocateVideoFrame(); if (video_frame_provider_client_) { stream_texture_proxy_->BindToLoop( stream_id_, video_frame_provider_client_, compositor_loop_); } } }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员变量video_frame_provider_client_指向的VideoFrameProviderClientImpl对象的创建过程可以参考前面的分析。WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded通过调用前面创建的StreamTextureProxyImpl对象的成员函数BindToLoop指定成员变量video_frame_provider_client_指向的VideoFrameProviderClientImpl对象在Render进程的Compositor线程中处理MediaPlayer的解码输出,如下所示:
void StreamTextureProxyImpl::BindToLoop( int32 stream_id, cc::VideoFrameProvider::Client* client, scoped_refptr<base::MessageLoopProxy> loop) { DCHECK(loop); { base::AutoLock lock(lock_); DCHECK(!loop_ || (loop == loop_)); loop_ = loop; client_ = client; } if (loop->BelongsToCurrentThread()) { BindOnThread(stream_id); return; } // Unretained is safe here only because the object is deleted on |loop_| // thread. loop->PostTask(FROM_HERE, base::Bind(&StreamTextureProxyImpl::BindOnThread, base::Unretained(this), stream_id)); }这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。
StreamTextureProxyImpl类的成员函数BindToLoop首先将参数client指向的VideoFrameProviderClientImpl对象保存在成员变量client_中,以便以后可以将MediaPlayer的解码输出交给它处理。
另外一个参数loop描述的是Render进程的Compoisitor线程的消息队列。StreamTextureProxyImpl类的成员函数BindToLoop接下来判断当前正在执行的线程是否就是Compositor线程。如果是的话,那么就直接调用另外一个成员函数BindToThread,用来在Compositor线程中设置一个Route,接收MediaPlayer的解码输出通知。如果不是的话,那么就需要向Compositor线程的消息队列发送一个Task。当这个Task在Compositor线程中执行时,再调用StreamTextureProxyImpl类的成员函数BindToThread中,用来确保在Compositor线程获得MediaPlayer的解码输出通知。
StreamTextureProxyImpl类的成员函数BindOnThread的实现如下所示:
void StreamTextureProxyImpl::BindOnThread(int32 stream_id) { host_->BindToCurrentThread(stream_id, this); }这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。
StreamTextureProxyImpl类的成员变量host_指向的是一个StreamTextureHost对象。StreamTextureProxyImpl类的成员函数BindOnThread调用这个StreamTextureHost对象的成员函数BindToCurrentThread让其在Compositor线程中接收MediaPlayer的解码输出通知,如下所示:
bool StreamTextureHost::BindToCurrentThread(int32 stream_id, Listener* listener) { listener_ = listener; if (channel_.get() && stream_id && !stream_id_) { stream_id_ = stream_id; channel_->AddRoute(stream_id, weak_ptr_factory_.GetWeakPtr()); channel_->Send(new GpuStreamTextureMsg_StartListening(stream_id)); return true; } return false; }这个函数定义在文件external/chromium_org/content/renderer/gpu/stream_texture_host_android.cc中。
从前面的调用过程可以知道,参数listener指向的是一个StreamTextureProxyImpl对象。StreamTextureHost类的成员函数BindToCurrentThread首先将这个StreamTextureProxyImpl对象保存在成员变量listener_中。
StreamTextureHost类的另外一个成员变量channel_描述的是一个GPU通道。这个GPU通道就是前面Render请求GPU进程创建SurfaceTexture所用的GPU通道。StreamTextureHost类的成员函数BindToCurrentThread将当前正在处理的StreamTextureHost对象设置为该GPU通道的一个Route,用来接收从GPU进程发送过来的Routing ID为stream_id的IPC消息,也就是那些与前面创建的SurfaceTexture相关的IPC消息。
StreamTextureHost类的成员函数最后通过成员变量channel_描述的GPU通道向GPU进程发送一个类型为GpuStreamTextureMsg_StartListening的IPC消息,表示Render进程已经准备就绪接收MediaPlayer的解码输出。
GPU进程通过StreamTexture类的成员函数OnMessageReceived接收类型为GpuStreamTextureMsg_StartListening的IPC消息,如下所示:
bool StreamTexture::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(StreamTexture, message) IPC_MESSAGE_HANDLER(GpuStreamTextureMsg_StartListening, OnStartListening) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() DCHECK(handled); return handled; }这个函数定义在文件external/chromium_org/content/common/gpu/stream_texture_android.cc中。
StreamTexture类的成员函数OnMessageReceived将类型为StreamTextureMsg_StartListening的IPC消息分发给另外一个成员函数OnStartListening处理,如下所示:
void StreamTexture::OnStartListening() { DCHECK(!has_listener_); has_listener_ = true; }这个函数定义在文件external/chromium_org/content/common/gpu/stream_texture_android.cc中。
StreamTexture类的成员函数OnStartListening将成员变量has_listener_的值设置为true,表示Render进程准备就绪接收MediaPlayer的解码输出。
这一步执行完成之后,Chromium就为<video>标签创建了一个SurfaceTexture对象,并且在Render进程中准备就绪接收MediaPlayer的解码输出。回到前面分析的WebMediaPlayerAndroid类的成员函数play中,它接下来就会将前面创建的SurfaceTexture对象设置为MediaPlayer的解码输出。这是通过调用WebMediaPlayerAndroid类的另外一个成员函数EstablishSurfaceTexturePeer实现的,如下所示:
void WebMediaPlayerAndroid::EstablishSurfaceTexturePeer() { ...... if (stream_texture_factory_.get() && stream_id_) stream_texture_factory_->EstablishPeer(stream_id_, player_id_); ...... }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
WebMediaPlayerAndroid类的成员函数EstablishSurfaceTexturePeer调用成员变量stream_texture_factory_指向的StreamTextureFactoryImpl对象的成员函数EstablishPeer将之前创建的SurfaceTexture对象设置为MediaPlayer的解码输出,如下所示:
void StreamTextureFactoryImpl::EstablishPeer(int32 stream_id, int player_id) { DCHECK(channel_.get()); channel_->Send( new GpuStreamTextureMsg_EstablishPeer(stream_id, frame_id_, player_id)); }这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。
StreamTextureFactoryImpl类的成员函数EstablishPeer通过成员变量channel_描述的GPU通道向GPU进程发送一个类型为GpuStreamTextureMsg_EstablishPeer的IPC消息,请求它将之前创建的SurfaceTexture对象设置为MediaPlayer的解码输出。
GPU进程通过StreamTexture类的成员函数OnMessageReceived接收类型为GpuStreamTextureMsg_EstablishPeer的IPC消息,如下所示:
bool StreamTexture::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(StreamTexture, message) ...... IPC_MESSAGE_HANDLER(GpuStreamTextureMsg_EstablishPeer, OnEstablishPeer) ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() DCHECK(handled); return handled; }这个函数定义在文件external/chromium_org/content/common/gpu/stream_texture_android.cc中。
StreamTexture类的成员函数OnMessageReceived将类型为GpuStreamTextureMsg_EstablishPeer的IPC消息分发给另外一个成员函数OnEstablishPeer处理,如下所示:
void StreamTexture::OnEstablishPeer(int32 primary_id, int32 secondary_id) { ...... SurfaceTexturePeer::GetInstance()->EstablishSurfaceTexturePeer( process, surface_texture_, primary_id, secondary_id); }这个函数定义在文件external/chromium_org/content/common/gpu/stream_texture_android.cc中。
从前面的分析可以知道,StreamTexture类的成员变量surface_texture_指向的SurfaceTexture对象就是之前Render进程请求GPU进程创建的SurfaceTexture对象。现在需要将这个SurfaceTexture对象设置为MediaPlayer的解码输出。这是通过调用运行在GPU进程中的一个SurfaceTexturePeer单例对象的成员函数EstablishSurfaceTexturePeer实现的。
SurfaceTexturePeer类的成员函数EstablishSurfaceTexturePeer的实现如下所示:
void SurfaceTexturePeerBrowserImpl::EstablishSurfaceTexturePeer( base::ProcessHandle render_process_handle, scoped_refptr<gfx::SurfaceTexture> surface_texture, int render_frame_id, int player_id) { ...... BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( &SetSurfacePeer, surface_texture, render_process_handle, render_frame_id, player_id)); }这个函数定义在文件external/chromium_org/content/browser/android/surface_texture_peer_browser_impl.cc中。
在Android平台中,Chromium的GPU进程与Browser进程实际上是同一个进程。SurfaceTexturePeer类的成员函数EstablishSurfaceTexturePeer于是就在Browser进程的UI线程中执行函数SetSurfacePeer,目的是将参数surface_texture描述的SurfaceTexture对象设置为MediaPlayer的解码输出。
函数SetSurfacePeer的实现如下所示:
static void SetSurfacePeer( scoped_refptr<gfx::SurfaceTexture> surface_texture, base::ProcessHandle render_process_handle, int render_frame_id, int player_id) { int render_process_id = 0; RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator(); while (!it.IsAtEnd()) { if (it.GetCurrentValue()->GetHandle() == render_process_handle) { render_process_id = it.GetCurrentValue()->GetID(); break; } it.Advance(); } ...... RenderFrameHostImpl* frame = RenderFrameHostImpl::FromID(render_process_id, render_frame_id); ...... RenderViewHostImpl* view = static_cast<RenderViewHostImpl*>(frame->GetRenderViewHost()); BrowserMediaPlayerManager* player_manager = view->media_web_contents_observer()->GetMediaPlayerManager(frame); ...... media::MediaPlayerAndroid* player = player_manager->GetPlayer(player_id); ...... if (player != player_manager->GetFullscreenPlayer()) { gfx::ScopedJavaSurface scoped_surface(surface_texture); player->SetVideoSurface(scoped_surface.Pass()); } }
这个函数定义在文件external/chromium_org/content/browser/android/surface_texture_peer_browser_impl.cc中。
从前面Chromium为视频标签<video>创建播放器的过程分析一文可以知道,Render进程在解析当前正在加载的网页的<video>标签时,会请求Browser进程分别为它们创建一个MediaPlayerBridge对象。这些MediaPlayerBridge对象描述的是Browser进程为<video>标签提供的播放器接口。每一个MediaPlayerBridge对象在Java层都会创建一个由Android系统提供的播放器实例,也就是一个MediaPlayer对象。
函数SetSurfacePeer要做的事情就是在Browser进程找到ID值为player_id的一个MediaPlayerBridge对象,然后将参数surface_texture描述的SurfaceTexture对象设置为它在Java层创建的MediaPlayer的解码输出。
从前面Chromium为视频标签<video>创建播放器的过程分析一文可以知道,在Render进程中创建的每一个播放器实例,也就是每一个MediaPlayerAndroid对象,都有一个Player ID。这些Player ID只在当前的Render进程中唯一(Chromium可能存多个Render进程)。因此,函数SetSurfacePeer不能根据仅仅根据参数player_id就能找到其对应的MediaPlayerBridge对象。
为了找到目标MediaPlayerBridge对象,函数SetSurfacePeer首先要找到目标MediaPlayerBridge对象所对应的Render进程。这可以通过参数render_process_handle获得。然而,知道目标MediaPlayerBridge对象所对应的Render进程还不足够,因为一个Render进程可能会同时加载多个网页。Player ID在同一个网页内才是唯一的。这时候需要用到另外一个参数render_frame_id。这个参数render_frame_id描述的是目标MediaPlayerBridge对象所对应的网页。
Browser为同一个网页的<video>标签创建的所有MediaPlayerBridge对象都由同一个BrowserMediaPlayerManager对象管理。知道了目标MediaPlayerBridge对象所对应的Render进程和网页之后,就可以获得这个BrowserMediaPlayerManager对象。有了这个BrowserMediaPlayerManager对象之后,就可以调用它的成员函数GetPlayer获得与参数player_id对应的MediaPlayerBridge对象。
有了这个MediaPlayerBridge对象之后,就可以调用它的成员函数SetVideoSurface将参数surface_texture描述的SurfaceTexture对象设置为它在Java层创建的MediaPlayer的解码输出了。不过,只有在<video>标签没有启动全屏播放的时候,才可以设置。这是因为在全屏播放模式下,MediaPlayer的解码输出是交给一个全屏模式的SurfaceView渲染的。这一点我们在接下来一篇文章中再详细分析。
这里还有一点需要注意,MediaPlayerBridge类是从MediaPlayerAndroid类继承下来的,因此,函数SetSurfacePeer可以将找到的目标MediaPlayerBridge对象保存在一个MediaPlayerAndroid指针中。
在将参数surface_texture描述的SurfaceTexture对象设置为它在Java层创建的MediaPlayer的解码输出之前,函数SetSurfacePeer需要将这个SurfaceTexture对象封装在一个Surface对象。这是通过ScopedJavaSurface类实现的,如下所示:
ScopedJavaSurface::ScopedJavaSurface( const SurfaceTexture* surface_texture) : auto_release_(true), is_protected_(false) { JNIEnv* env = base::android::AttachCurrentThread(); RegisterNativesIfNeeded(env); ScopedJavaLocalRef<jobject> tmp(JNI_Surface::Java_Surface_Constructor( env, surface_texture->j_surface_texture().obj())); DCHECK(!tmp.is_null()); j_surface_.Reset(tmp); }这个函数定义在文件external/chromium_org/ui/gl/android/scoped_java_surface.cc中。
ScopedJavaSurface类的构造函数首先是调用参数surface_texture指向的一个C++层的SurfaceTexture对象的成员函数j_surface_texture获得它在Java层对应的SurfaceTexture对象,然后再以这个Java层的SurfaceTexture为参数,调用JNI接口JNI_Surface::Java_Surface_Constructor在Java层创建一个Surface对象。Java层Surface对象的创建接口,可以参考Android SDK文档。创建出来的Java层Surface对象,保存在ScopedJavaSurface类的成员变量j_surface_中。
以后通过调用ScopedJavaSurface类的成员函数j_surface可以获得保存在成员变量j_surface_中的Java层Surface对象,如下所示:
class GL_EXPORT ScopedJavaSurface { ...... public: ...... const base::android::JavaRef<jobject>& j_surface() const { return j_surface_; } private: ...... base::android::ScopedJavaGlobalRef<jobject> j_surface_; };这个函数定义在文件external/chromium_org/ui/gl/android/scoped_java_surface.h中。
回到前面分析的函数SetSurfacePeer中,它将参数surface_texture描述的SurfaceTexture对象封装在一个Java层Surface对象之后,接下来就调用前面找到的目标MediaPlayerBridge对象的成员函数SetVideoSurface将其设置为Java层MediaPlayer的解码输出,如下所示:
void MediaPlayerBridge::SetVideoSurface(gfx::ScopedJavaSurface surface) { if (j_media_player_bridge_.is_null()) { if (surface.IsEmpty()) return; Prepare(); } JNIEnv* env = base::android::AttachCurrentThread(); CHECK(env); is_surface_in_use_ = true; Java_MediaPlayerBridge_setSurface( env, j_media_player_bridge_.obj(), surface.j_surface().obj()); }这个函数定义在文件external/chromium_org/media/base/android/media_player_bridge.cc中。
从前面Chromium为视频标签<video>创建播放器的过程分析一文可以知道,MediaPlayerBridge类的成员变量j_media_player_bridge_指向的是Java层的一个MediaPlayerBridge对象。如果这个MediaPlayerBridge对象还没有创建的,那么C++层的MediaPlayerBridge类的成员函数SetVideoSurface就会先调用另外一个成员函数Prepare进行创建。
在我们这个情景中,MediaPlayerBridge类的成员变量j_media_player_bridge_指向的Java层MediaPlayerBridge对象已经创建。这时候C++层的MediaPlayerBridge类的成员函数SetVideoSurface就会通过JNI接口Java_MediaPlayerBridge_setSurface调用这个Java层MediaPlayerBridge对象的成员函数setSurface将参数surface描述的Java层Surface设置为它内部创建的MediaPlayer的解码输出。
Java层的MediaPlayerBridge类的成员函数setSurface的实现如下所示:
public class MediaPlayerBridge { ...... @CalledByNative protected void setSurface(Surface surface) { getLocalPlayer().setSurface(surface); } ...... }这个函数定义在文件external/chromium_org/media/base/android/java/src/org/chromium/media/MediaPlayerBridge.java中。
MediaPlayerBridge类的成员函数setSurface首先调用另外一个成员函数getLocalPlayer获得内部创建的一个MediaPlayer对象。这个MediaPlayer对象描述的就是Android系统提供的播放器实例,它的创建过程可以参考前面Chromium为视频标签<video>创建播放器的过程分析一文。有了这个MediaPlayer对象之后,MediaPlayerBridge类的成员函数setSurface就将参数surface描述的Surface对象设置为它的解码输出。
从前面的分析可以知道,参数surface描述的Surface对象封装了一个SurfaceTexture对象。当MediaPlayer开始播放视频时,它每解码出来的一帧,都会从上述被封装的SurfaceTexture对象中获得一个GPU缓冲区,并且将视频帧数据写入到该GPU缓冲区中,然后再通过设置给上述被封装的SurfaceTexture对象的Frame Available Listener,调用C++层的StreamTexture类的成员函数OnFrameAvailable,用来通知它MediaPlayer有新的解码输出需要渲染。
C++层的StreamTexture类的成员函数OnFrameAvailable的实现如下所示:
void StreamTexture::OnFrameAvailable() { has_pending_frame_ = true; if (has_listener_ && owner_stub_) { owner_stub_->channel()->Send( new GpuStreamTextureMsg_FrameAvailable(route_id_)); } }
这个函数定义在文件external/chromium_org/content/common/gpu/stream_texture_android.cc中。
StreamTexture类的成员函数OnFrameAvailable首先将成员变量has_pending_frame_的值设置为true,表示有一个刚刚解码出来的视频帧待处理。
从前面的分析可以知道,StreamTexture类的成员变量has_listener_已经被设置为true。在这种情况下,如果StreamTexture类的成员变量owner_stub_指向了一个GpuCommandBufferStub对象,那么就说明当前正在处理的StreamTexture对象是为Render进程创建的。这时候就需要向该Render进程发送一个类型为GpuStreamTextureMsg_FrameAvailable的IPC消息,用来通知它MediaPlayer有一个新的解码输出。
Render进程通过StreamTextureHost类的成员函数OnMessageReceived接收类型为GpuStreamTextureMsg_FrameAvailable的IPC消息,如下所示:
bool StreamTextureHost::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(StreamTextureHost, message) IPC_MESSAGE_HANDLER(GpuStreamTextureMsg_FrameAvailable, OnFrameAvailable); ...... IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() DCHECK(handled); return handled; }这个函数定义在文件external/chromium_org/content/renderer/gpu/stream_texture_host_android.cc中。
StreamTextureHost类的成员函数OnMessageReceived将类型为GpuStreamTextureMsg_FrameAvailable的IPC消息分发给另外一个成员函数OnFrameAvailable处理,如下所示:
void StreamTextureHost::OnFrameAvailable() { if (listener_) listener_->OnFrameAvailable(); }这个函数定义在文件external/chromium_org/content/renderer/gpu/stream_texture_host_android.cc中。
从前面的分析可以知道,StreamTextureHost类的成员变量listener_指向的是一个StreamTextureProxyImpl对象。 StreamTextureHost类的成员函数OnFrameAvailable调用这个StreamTextureProxyImpl对象的成员函数OnFrameAvailable通知它MediaPlayer有一个新的解码输出,如下所示:
void StreamTextureProxyImpl::OnFrameAvailable() { base::AutoLock lock(lock_); if (client_) client_->DidReceiveFrame(); }这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。
从前面的分析可以知道,StreamTextureProxyImpl类的成员变量client_指向的是一个VideoFrameProviderClientImpl对象。StreamTextureProxyImpl类的成员函数OnFrameAvailable调用这个VideoFrameProviderClientImpl对象的成员函数DidReceiveFrame通知它MediaPlayer有一个新的解码输出,如下所示:
void VideoFrameProviderClientImpl::DidReceiveFrame() { ...... if (active_video_layer_) active_video_layer_->SetNeedsRedraw(); }这个函数定义在文件external/chromium_org/cc/layers/video_frame_provider_client_impl.cc中。
VideoFrameProviderClientImpl类的成员变量active_video_layer_指向的是一个VideoLayerImpl对象。这个VideoLayerImpl对象描述的就是<video>标签在网页的CC Active Layer Tree中对应的节点。VideoFrameProviderClientImpl类的成员函数DidReceiveFrame调用这个VideoLayerImpl对象的成员函数SetNeedsRedraw请求调度器对CC Active Layer Tree进行重新渲染,以便将MediaPlayer新的解码输出显示出来。
从前面Chromium硬件加速渲染的UI合成过程分析一文可以知道,当网页的CC Active Layer Tree被渲染时,它里面的每一个Layer的成员函数WillDraw和AppendQuads都会被调用,用来准备要渲染的材料,以便发送给Browser进程进行合成。对于<video>标签来说,它在CC Active Layer Tree中对应的是类型为VideoLayerImpl的Layer。因此,当CC Active Layer Tree被重新渲染时,VideoLayerImpl类的成员函数WillDraw和AppendQuads就会被调用。接下来我们继续分析VideoLayerImpl类的成员函数WillDraw和AppendQuads的实现,以便了解<video>标签的视频画面渲染过程。
VideoLayerImpl类的成员函数WillDraw的实现如下所示:
bool VideoLayerImpl::WillDraw(DrawMode draw_mode, ResourceProvider* resource_provider) { ...... frame_ = provider_client_impl_->AcquireLockAndCurrentFrame(); ...... if (!updater_) { updater_.reset( new VideoResourceUpdater(layer_tree_impl()->context_provider(), layer_tree_impl()->resource_provider())); } VideoFrameExternalResources external_resources = updater_->CreateExternalResourcesFromVideoFrame(frame_); frame_resource_type_ = external_resources.type; ...... for (size_t i = 0; i < external_resources.mailboxes.size(); ++i) { unsigned resource_id = resource_provider->CreateResourceFromTextureMailbox( external_resources.mailboxes[i], SingleReleaseCallback::Create(external_resources.release_callbacks[i])); frame_resources_.push_back(resource_id); } return true; }这个函数定义在文件external/chromium_org/cc/layers/video_layer_impl.cc中。
从前面的分析可以知道,VideoLayerImpl类的成员变量provider_client_impl_指向的是一个VideoFrameProviderClientImpl对象。VideoLayerImpl类的成员函数WillDraw调用这个VideoFrameProviderClientImpl对象的成员函数AcquireLockAndCurrentFrame获得一个视频帧,如下所示:
scoped_refptr<media::VideoFrame> VideoFrameProviderClientImpl::AcquireLockAndCurrentFrame() { provider_lock_.Acquire(); // Balanced by call to ReleaseLock(). if (!provider_) return NULL; return provider_->GetCurrentFrame(); }这个函数定义在文件external/chromium_org/cc/layers/video_frame_provider_client_impl.cc中。
从前面的分析可以知道,VideoFrameProviderClientImpl类的成员变量provider_指向的是一个WebMediaPlayerAndroid对象。VideoFrameProviderClientImpl类的成员函数AcquireLockAndCurrentFrame调用这个WebMediaPlayerAndroid对象的成员函数GetCurrentFrame获得一个视频帧,如下所示:
scoped_refptr<media::VideoFrame> WebMediaPlayerAndroid::GetCurrentFrame() { scoped_refptr<VideoFrame> video_frame; { base::AutoLock auto_lock(current_frame_lock_); video_frame = current_frame_; } ....... return video_frame; }这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。
从前面的分析可以知道,WebMediaPlayerAndroid类的成员变量current_frame_指向的是一个VideoFrame对象。WebMediaPlayerAndroid类的成员函数GetCurrentFrame将这个这个VideoFrame对象返回给调用者。
这个VideoFrame对象封装了一个Mailbox。这个Mailbox描述的是一个纹理。前面我们使用这个纹理创建了一个SurfaceTexture对象,并且使用这个SurfaceTexture对象作为MediaPlayer的解码输出。因此,有了这个VideoFrame对象,就可以访问MediaPlayer的解码输出。
这一步执行完成后,回到前面分析的VideoLayerImpl类的成员函数WillDraw中,它获得了一个VideoFrame对象之后,接下来检查成员变量updater_是否指向了一个VideoResourceUpdater对象。如果没有,那么就会先创建一个VideoResourceUpdater对象,并且保存在成员变量updater_中。
有了上述VideoResourceUpdater对象之后,VideoLayerImpl类的成员函数WillDraw就调用它的成员函数CreateExternalResourcesFromVideoFrame根据前面获得的VideoFrame对象创建一个外部资源对象,如下所示:
VideoFrameExternalResources VideoResourceUpdater:: CreateExternalResourcesFromVideoFrame( const scoped_refptr<media::VideoFrame>& video_frame) { ...... if (video_frame->format() == media::VideoFrame::NATIVE_TEXTURE) return CreateForHardwarePlanes(video_frame); else return CreateForSoftwarePlanes(video_frame); }这个函数定义在文件external/chromium_org/cc/resources/video_resource_updater.cc中。
从前面的分析可以知道,参数video_frame指向的VideoFrame对象描述的是一个纹理。这时候VideoResourceUpdater类的成员函数CreateExternalResourcesFromVideoFrame会调用另外一个成员函数CreateForHardwarePlanes为它创建一个外部资源对象,如下所示:
VideoFrameExternalResources VideoResourceUpdater::CreateForHardwarePlanes( const scoped_refptr<media::VideoFrame>& video_frame) { ...... const gpu::MailboxHolder* mailbox_holder = video_frame->mailbox_holder(); VideoFrameExternalResources external_resources; switch (mailbox_holder->texture_target) { case GL_TEXTURE_2D: external_resources.type = VideoFrameExternalResources::RGB_RESOURCE; break; case GL_TEXTURE_EXTERNAL_OES: external_resources.type = VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE; break; case GL_TEXTURE_RECTANGLE_ARB: external_resources.type = VideoFrameExternalResources::IO_SURFACE; break; default: NOTREACHED(); return VideoFrameExternalResources(); } external_resources.mailboxes.push_back( TextureMailbox(mailbox_holder->mailbox, mailbox_holder->texture_target, mailbox_holder->sync_point)); ...... return external_resources; }这个函数定义在文件external/chromium_org/cc/resources/video_resource_updater.cc中。
参数video_frame指向的VideoFrame对象描述的纹理对象的类型为GL_TEXTURE_EXTERNAL_OES,因此VideoResourceUpdater类的成员函数CreateForHardwarePlanes接下来会创建一个类型为VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE的外部资源对象。这个外部资源对象有一个TextureMailbox列表。VideoResourceUpdater类的成员函数CreateForHardwarePlanes往这个列表中添加了一个TextureMailbox对象。这个TextureMailbox对象引用了参数video_frame指向的VideoFrame对象所描述的纹理对象。
这一步执行完成后,回到前面分析的VideoLayerImpl类的成员函数WillDraw中,这时候它的成员变量frame_resource_type_ 的值就会被设置为VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE。
VideoLayerImpl类的成员函数WillDraw接下来遍历前面获得的外部资源对象的Mailbox列表。对于列表里面的每一个MailboxTexture对象,VideoLayerImpl类的成员函数WillDraw都会调用参数resource_provider指向的一个ResourceProvider对象的成员函数CreateResourceFromTextureMailbox为其创建一个资源对象,如下所示:
ResourceProvider::ResourceId ResourceProvider::CreateResourceFromTextureMailbox( const TextureMailbox& mailbox, scoped_ptr<SingleReleaseCallback> release_callback) { ...... ResourceId id = next_id_++; DCHECK(mailbox.IsValid()); Resource& resource = resources_[id]; if (mailbox.IsTexture()) { resource = Resource(0, gfx::Size(), Resource::External, mailbox.target(), GL_LINEAR, 0, GL_CLAMP_TO_EDGE, TextureUsageAny, RGBA_8888); } ...... resource.allocated = true; resource.mailbox = mailbox; resource.release_callback = base::Bind(&SingleReleaseCallback::Run, base::Owned(release_callback.release())); resource.allow_overlay = mailbox.allow_overlay(); return id; }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
从前面的分析可以知道,参数mailbox指向的TextureMailbox对象描述的是一个纹理。这时候ResourceProvider类的成员函数CreateResourceFromTextureMailbox就会为它创建一个类型为Resource::External的资源对象,并且将这个资源对象的ID返回给调用者。注意,这里创建的资源对象关联有一个Mailbox。这个Mailbox由参数mailbox指向的TextureMailbox对象提供,通过它描述的纹理可以访问MediaPlayer的解码输出。
这一步执行完成后,回到前面分析的VideoLayerImpl类的成员函数WillDraw中,这时候它的成员变量frame_resources_描述的一个std::vector就包含了一个资源ID。这个资源ID描述的是一个类型为Resource::External的资源对象。通过这个资源对象关联的Mailbox可以访问MediaPlayer的解码输出。
VideoLayerImpl类的成员函数WillDraw执行完成之后,我们就为<video>标签准备好一个类型为Resource::External的资源对象。这个资源对象接下来又会通过VideoLayerImpl类的成员函数AppendQuads封装在一个StreamVideoDrawQuad对象中,如下所示:
void VideoLayerImpl::AppendQuads(QuadSink* quad_sink, AppendQuadsData* append_quads_data) { ...... switch (frame_resource_type_) { ...... case VideoFrameExternalResources::STREAM_TEXTURE_RESOURCE: { ...... gfx::Transform scale; scale.Scale(tex_width_scale, tex_height_scale); scoped_ptr<StreamVideoDrawQuad> stream_video_quad = StreamVideoDrawQuad::Create(); stream_video_quad->SetNew( shared_quad_state, quad_rect, opaque_rect, visible_quad_rect, frame_resources_[0], scale * provider_client_impl_->stream_texture_matrix()); quad_sink->Append(stream_video_quad.PassAs<DrawQuad>()); break; } ...... } }这个函数定义在文件external/chromium_org/cc/resources/resource_provider.cc中。
这个StreamVideoDrawQuad对象与前面创建的类型为Resource::External的资源对象关联,并且描述了这个资源对象所引用的纹理的绘制信息,例如它的纹理坐标、变换矩阵等。由于这个纹理对应的是MediaPlayer的解码输出,因此这里创建的StreamVideoDrawQuad对象实际上描述的是如何将MediaPlayer的解码输出绘制在网页上。
最后,VideoLayerImpl类的成员函数AppendQuads会将创建出来的StreamVideoDrawQuad对象保存在参数append_quads_data指向的一个QuadSink对象中。这个QuadSink对象保存了一系列的DrawQuad。每一个DrawQuad描述的都是网页的某一部分的渲染信息。对于StreamVideoDrawQuad对象来说,它实际上是一个类型为STREAM_VIDEO_CONTENT的DrawQuad。这些DrawQuad会发送给Browser进程进行渲染。
从前面Chromium硬件加速渲染的UI合成过程分析一文可以知道,Browser进程使用的渲染器由一个GLRenderer对象描述。这个GLRenderer对象会调用成员函数DoDrawQuad对Render进程发送过来的DrawQuad进行渲染,如下所示:
void GLRenderer::DoDrawQuad(DrawingFrame* frame, const DrawQuad* quad) { ...... switch (quad->material) { ...... case DrawQuad::STREAM_VIDEO_CONTENT: DrawStreamVideoQuad(frame, StreamVideoDrawQuad::MaterialCast(quad)); break; ...... } }这个函数定义在文件external/chromium_org/cc/output/gl_renderer.cc中。
对于类型为个类型为STREAM_VIDEO_CONTENT的DrawQuad,GLRenderer类的成员函数DoDrawQuad会调用另外一个成员函数DrawStreamVideoQuad对其进行渲染,如下所示:
void GLRenderer::DrawStreamVideoQuad(const DrawingFrame* frame, const StreamVideoDrawQuad* quad) { ...... const VideoStreamTextureProgram* program = GetVideoStreamTextureProgram(tex_coord_precision); SetUseProgram(program->program()); ToGLMatrix(&gl_matrix[0], quad->matrix); GLC(gl_, gl_->UniformMatrix4fv( program->vertex_shader().tex_matrix_location(), 1, false, gl_matrix)); ResourceProvider::ScopedReadLockGL lock(resource_provider_, quad->resource_id); DCHECK_EQ(GL_TEXTURE0, ResourceProvider::GetActiveTextureUnit(gl_)); GLC(gl_, gl_->BindTexture(GL_TEXTURE_EXTERNAL_OES, lock.texture_id())); GLC(gl_, gl_->Uniform1i(program->fragment_shader().sampler_location(), 0)); ...... DrawQuadGeometry(frame, quad->quadTransform(), quad->rect, program->vertex_shader().matrix_location()); }这个函数定义在文件external/chromium_org/cc/output/gl_renderer.cc中。
类型为STREAM_VIDEO_CONTENT的DrawQuad描述的是一个纹理,GLRenderer类的成员函数DrawStreamVideoQuad通过调用另外一个成员函数DrawQuadGeometry对这个纹理进行渲染,如下所示:
void GLRenderer::DrawQuadGeometry(const DrawingFrame* frame, const gfx::Transform& draw_transform, const gfx::RectF& quad_rect, int matrix_location) { gfx::Transform quad_rect_matrix; QuadRectTransform(&quad_rect_matrix, draw_transform, quad_rect); static float gl_matrix[16]; ToGLMatrix(&gl_matrix[0], frame->projection_matrix * quad_rect_matrix); GLC(gl_, gl_->UniformMatrix4fv(matrix_location, 1, false, &gl_matrix[0])); GLC(gl_, gl_->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0)); }这个函数定义在文件external/chromium_org/cc/output/gl_renderer.cc中。
GLRenderer类的成员变量gl_描述的一个Command Buffer GL接口,也就是一个GLES2Implementation对象。GLRenderer类的成员函数DrawQuadGeometry调用这个GLES2Implementation对象的成员函数DrawElements对参数frame描述的纹理进行渲染,实际上就是通过Command Buffer向GPU进程发送了一个gles2::cmds::DrawElements命令。
从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,GPU进程通过调用GLES2DecoderImpl类的成员函数DoDrawElements执行Render进程发送过来的gles2::cmds::DrawElements命令,如下所示:
error::Error GLES2DecoderImpl::DoDrawElements( const char* function_name, bool instanced, GLenum mode, GLsizei count, GLenum type, int32 offset, GLsizei primcount) { ...... if (IsDrawValid(function_name, max_vertex_accessed, primcount)) { ...... if (SimulateFixedAttribs( function_name, max_vertex_accessed, &simulated_fixed_attribs, primcount)) { bool textures_set = !PrepareTexturesForRender(); ...... ScopedRenderTo do_render(framebuffer_state_.bound_draw_framebuffer.get()); if (!instanced) { glDrawElements(mode, count, type, indices); } else { glDrawElementsInstancedANGLE(mode, count, type, indices, primcount); } ...... } ...... } return error::kNoError; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数DoDrawElements首先调用成员函数IsDrawValid检查当前正在处理的gles2::cmds::DrawElements命令指定的顶点信息是否有效。如果有效,GLES2DecoderImpl类的成员函数IsDrawValid的返回值就等于true。
GLES2DecoderImpl类的成员函数DoDrawElements接下来调用成员函数SimulateFixedAttribs检查当前正在处理的gles2::cmds::DrawElements命令是否使用了GLfixed类型的顶点属性。如果没有使用,或者使用了,并且当前加载的OpenGL库支持,那么GLES2DecoderImpl类的成员函数SimulateFixedAttribs的返回值就等于true。
通过了以上两个检查之后,GLES2DecoderImpl类的成员函数DoDrawElements接下来就会调用真正的OpenGL函数glDrawElements或者glDrawElementsInstancedANGLE执行当前正在处理的gles2::cmds::DrawElements命令。在我们这个情景中,就是绘制这个命令所指定的纹理。
不过,在调用OpenGL函数glDrawElements或者glDrawElementsInstancedANGLE之前,GLES2DecoderImpl类的成员函数DoDrawElements需要调用另外一个成员函数PrepareTexturesForRender做一些准备工作。例如,在我们这个情景中,要将MediaPlayer的最新解码输出设置为要绘制的纹理的后端存储。这样在绘制纹理的时候,才能将<video>标签的视频画面显示网页中。
GLES2DecoderImpl类的成员函数PrepareTexturesForRender的实现如下所示:
bool GLES2DecoderImpl::PrepareTexturesForRender() { ...... bool textures_set = false; const Program::SamplerIndices& sampler_indices = state_.current_program->sampler_indices(); for (size_t ii = 0; ii < sampler_indices.size(); ++ii) { const Program::UniformInfo* uniform_info = state_.current_program->GetUniformInfo(sampler_indices[ii]); ...... for (size_t jj = 0; jj < uniform_info->texture_units.size(); ++jj) { GLuint texture_unit_index = uniform_info->texture_units[jj]; if (texture_unit_index < state_.texture_units.size()) { TextureUnit& texture_unit = state_.texture_units[texture_unit_index]; TextureRef* texture_ref = texture_unit.GetInfoForSamplerType(uniform_info->type).get(); GLenum textarget = GetBindTargetForSamplerType(uniform_info->type); ...... if (textarget != GL_TEXTURE_CUBE_MAP) { Texture* texture = texture_ref->texture(); gfx::GLImage* image = texture->GetLevelImage(textarget, 0); if (image && !texture->IsAttachedToFramebuffer()) { ...... textures_set = true; glActiveTexture(GL_TEXTURE0 + texture_unit_index); image->WillUseTexImage(); continue; } } } // else: should this be an error? } } return !textures_set; }这个函数定义在文件external/chromium_org/gpu/command_buffer/service/gles2_cmd_decoder.cc中。
GLES2DecoderImpl类的成员函数PrepareTexturesForRender所做的事情就是遍历与当前所用的OpenGL Program相关的每一个纹理。对于那些类型不等于GL_TEXTURE_CUBE_MAP、关联有GLImage对象、不与FBO绑定(与FBO绑定意味着将内容绘制它里面去而不是将它的内容绘制出来)的纹理,GLES2DecoderImpl类的成员函数PrepareTexturesForRender将会调用它们关联的GLImage对象的成员函数WillUseTexImage,表示它们现在可以做一些准备工作,以便接下来可以进行绘制。
从前面的调用过程可以知道,当前我们要绘制的是一个类型为GL_TEXTURE_EXTERNAL_OES,并且关联有一个StreamTexture对象纹理。这个StreamTexture对象是前面Render进程请求GPU进程创建SurfaceTexture的过程中创建的。StreamTexture类是从GLImage类继承下来的,并且它重写了父类的成员函数WillUseTexImage。因此,接下来StreamTexture类的成员函数WillUseTexImage就会被调用,它的实现如下所示:
void StreamTexture::WillUseTexImage() { ...... if (has_pending_frame_) { ...... surface_texture_->UpdateTexImage(); ...... has_pending_frame_ = false; ...... } ...... }这个函数定义在文件external/chromium_org/content/common/gpu/stream_texture_android.cc中。
从前面的分析可以知道,当StreamTexture类的成员变量has_pending_frame_的值等于true的时候,就表示MediaPlayer解码了一个新的视频帧,并且这个新的视频帧正在等待处理。要做的处理就是将新的视频帧内容作为当前要绘制的纹理的底端存储。这样在绘制这个纹理的时候,才能将新解码出来的视频帧显示出来。这可以通过调用StreamTexture类的成员变量surface_texture_指向的一个C++层SurfaceTexture对象的成员函数UpdateTexImage实现。
每一个C++层SurfaceTexture对象在Java层都有一个对应的SurfaceTexture对象。当C++层SurfaceTexture对象的成员函数UpdateTexImage被调用时,它会通过JNI调用它在Java层对应的SurfaceTexture对象的成员函数updateTexImage。这个Java层的SurfaceTexture对象前面已经被设置为MediaPlayer的解码输出。因此,这一步执行完成之后,MediaPlayer新解码出来的视频帧就会被设置为当前要绘制的纹理的底端存储。
至此,我们就分析完成了<video>标签在非全屏播放模式下的视频画面渲染过程。非全屏播放模式,意味着<video>标签的视频画面要嵌入在网页中显示。Chromium通过SurfaceTexture获得MediaPlayer的解码输出,然后再将SurfaceTexture以纹理的方式进行渲染,从而完成将<video>标签的视频画面嵌入在网页中显示的目标。
在全屏播放模式下,<video>标签的视频画面使用另外一种完全不同的渲染方式。它们将会显示在一个独立的、全屏的窗口中。在Android平台上,这个窗口就是一个SurfaceView。在接下来一篇文章中,我们就详细分析<video>标签的视频画面在全屏播放模式下的渲染过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。
Chromium为视频标签<video>渲染视频画面的过程分析
原文:http://blog.csdn.net/luoshengyang/article/details/52082146