Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Chromium為視頻標簽(video)渲染視頻畫面的過程分析

Chromium為視頻標簽(video)渲染視頻畫面的過程分析

編輯:關於Android編程

浏覽器是否能哆無縫地渲染播放器的輸出,取決於播放器是否有良好的設計。一個有良好設計的播放器要有獨立的輸入和輸出。輸入就是一個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的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為每一個

標簽在CC Layer Tree創建一個VideoLayer。這個VideoLayer在CC Active Layer Tree中有一個對應的VideoLayerImpl。由於網頁的UI最終是通過渲染CC Active Layer Tree得到的,因此Chromium通過VideoLayerImpl接收MediaPayer的解碼輸出。

接下來,我們就先分析Chromium為

標簽在CC Layer Tree和CC Active Layer Tree中創建VideoLayer和VideoLayerImpl的過程,然後再分析MediaPlayer將解碼輸出交給VideoLayerImpl渲染的過程。

一文可以知道,當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_描述的是

標簽在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::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對象。對於

標簽來說,它在CC Layer Tree中對應的是一個VideoLayer對象,這個VideoLayer對象在CC Pending Layer Tree中對應的是一個VideoLayerImpl對象。這個VideoLayerImpl對象是通過調用VideoLayer類的成員函數CreateLayerImpl創建的,如下所示:

 

scoped_ptr VideoLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) {
  return VideoLayerImpl::Create(tree_impl, id(), provider_).PassAs();
}
這個函數定義在文件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中為

標簽創建了一個類型為VideoLayerImpl的Layer。這是通過調用VideoLayerImpl類的靜態成員函數Create實現的,如下所示:

 

scoped_ptr VideoLayerImpl::Create(
    LayerTreeImpl* tree_impl,
    int id,
    VideoFrameProvider* provider) {
  scoped_ptr 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::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 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就為

標簽在CC Pending Layer Tree中創建了一個VideoLayerImpl對象。從前面Chromium網頁Pending Layer Tree激活為Active Layer Tree的過程分析一文可以知道,這個VideoLayerImpl對象在CCPending Layer Tree激活為CC Active Layer Tree的時候,會變成CC Active Layer Tree中的一個節點。

這樣,Chromium就為

標簽在CC Active Layer Tree中創建了一個類型為VideoLayerImpl的Layer。接下來我們繼續分析Chromium創建SurfaceTexture接收MediaPlayer的解碼輸出的過程。

從前面Chromium為視頻標簽

創建播放器的過程分析 一文可以知道,獲得了 標簽要播放的視頻的元數據之後,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為視頻標簽

創建播放器的過程分析 一文。這裡我們主要它關注它調用的另外兩個成員函數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為視頻標簽

創建播放器的過程分析 一文,它內部包含有一個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 BufferOpenGL接口的成員函數GenTextures創建。

StreamTextureFactoryImpl類的成員變量context_provider_指向的是一個ContextProviderCommandBuffer對象。調用這個ContextProviderCommandBuffer對象的成員函數ContextGL可以獲得一個Command Buffer GL接口。當我們調用這個Command Buffer GL接口的成員函數執行GPU命令時,它實際上是通過Command Buffer將GPU命令發送給GPU進程執行。

獲得了一個紋理ID之後,StreamTextureFactoryImpl類的成員函數CreateStreamTexture就繼續調用上述Command Buffer GL接口的成員函數CreateStreamTextureCHROMIUM請求創建GPU進程創建一個SurfaceTexutre對象。

創建了SurfaceTexutre對象之後,StreamTextureFactoryImpl類的成員函數CreateStreamTexture還會為前面創建出來的紋理創建一個Mailbox。這個Mailbox的作用是將與它關聯的紋理發送給Browser進程進行合成,以便顯示在浏覽器窗口中。這個紋理描述的實際上就是MediaPlayer的解碼輸出,因此,上述Mailbox是用來將MediaPlayer的解碼輸出交給Browser進程合成顯示在浏覽器窗口中。

接下來我們繼續分析Render進程請求GPU進程創建SurfaceTexture對象的過程,也就是Command Buffer GL接口的成員函數CreateStreamTextureCHROMIUM的實現。

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中。

 

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 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 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時,表示

標簽的視頻在一個遠程設備上播放,也就是不在本設備上播放。我們也不考慮這一種情況。

從前面的調用過程可以知道,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& 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 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就為

標簽創建了一個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 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 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(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中。

創建播放器的過程分析 一文可以知道,Render進程在解析當前正在加載的網頁的 標簽時,會請求Browser進程分別為它們創建一個MediaPlayerBridge對象。這些MediaPlayerBridge對象描述的是Browser進程為 標簽提供的播放器接口。每一個MediaPlayerBridge對象在Java層都會創建一個由Android系統提供的播放器實例,也就是一個MediaPlayer對象。

函數SetSurfacePeer要做的事情就是在Browser進程找到ID值為player_id的一個MediaPlayerBridge對象,然後將參數surface_texture描述的SurfaceTexture對象設置為它在Java層創建的MediaPlayer的解碼輸出。

 

創建播放器的過程分析 一文可以知道,在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為同一個網頁的

標簽創建的所有MediaPlayerBridge對象都由同一個BrowserMediaPlayerManager對象管理。知道了目標MediaPlayerBridge對象所對應的Render進程和網頁之後,就可以獲得這個BrowserMediaPlayerManager對象。有了這個BrowserMediaPlayerManager對象之後,就可以調用它的成員函數GetPlayer獲得與參數player_id對應的MediaPlayerBridge對象。

有了這個MediaPlayerBridge對象之後,就可以調用它的成員函數SetVideoSurface將參數surface_texture描述的SurfaceTexture對象設置為它在Java層創建的MediaPlayer的解碼輸出了。不過,只有在

標簽沒有啟動全屏播放的時候,才可以設置。這是因為在全屏播放模式下,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 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& j_surface() const {
    return j_surface_;
  }

 private:
  ......

  base::android::ScopedJavaGlobalRef 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中。

 

創建播放器的過程分析 一文可以知道,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系統提供的播放器實例

創建播放器的過程分析 一文。有了這個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對象描述的就是

標簽在網頁的CC Active Layer Tree中對應的節點。VideoFrameProviderClientImpl類的成員函數DidReceiveFrame調用這個VideoLayerImpl對象的成員函數SetNeedsRedraw請求調度器對CC Active Layer Tree進行重新渲染,以便將MediaPlayer新的解碼輸出顯示出來。

當網頁的CC Active Layer Tree被渲染時,它裡面的每一個Layer的成員函數WillDraw和AppendQuads都會被調用,用來准備要渲染的材料,以便發送給Browser進程進行合成。對於

標簽來說,它在CC Active Layer Tree中對應的是類型為VideoLayerImpl的Layer。因此,當CC Active Layer Tree被重新渲染時,VideoLayerImpl類的成員函數WillDraw和AppendQuads就會被調用。接下來我們繼續分析VideoLayerImpl類的成員函數WillDraw和AppendQuads的實現,以便了解 標簽的視頻畫面渲染過程。

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
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 WebMediaPlayerAndroid::GetCurrentFrame() {
  scoped_refptr 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& 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& 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 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執行完成之後,我們就為

標簽准備好一個類型為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 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());
      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命令。

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的最新解碼輸出設置為要繪制的紋理的後端存儲。這樣在繪制紋理的時候,才能將

標簽的視頻畫面顯示網頁中。

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新解碼出來的視頻幀就會被設置為當前要繪制的紋理的底端存儲。

至此,我們就分析完成了

標簽在非全屏播放模式下的視頻畫面渲染過程。非全屏播放模式,意味著 標簽的視頻畫面要嵌入在網頁中顯示。Chromium通過SurfaceTexture獲得MediaPlayer的解碼輸出,然後再將SurfaceTexture以紋理的方式進行渲染,從而完成將 標簽的視頻畫面嵌入在網頁中顯示的目標。

在全屏播放模式下,

標簽的視頻畫面使用另外一種完全不同的渲染方式。它們將會顯示在一個獨立的、全屏的窗口中。在Android平台上,這個窗口就是一個SurfaceView。在接下來一篇文章中,我們就詳細分析 標簽的視頻畫面在全屏播放模式下的渲染過程,敬請關注!更多的信息也可以關注老羅的新浪微博:http://weibo.com/shengyangluo。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved