From 84a661708faa4431cdba5833a33c7834389c65ef Mon Sep 17 00:00:00 2001
From: "wjia@google.com" <wjia@google.com@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Mon, 7 Jun 2010 20:05:42 +0000
Subject: add support of OpenMAX EGL image decoding add support of GLES2 EGL
 image rendering OpenMAX system memory decoding almost works expect flow
 control in video renderer base

BUG=none
TEST=dev platform

Review URL: http://codereview.chromium.org/2456006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49089 0039d316-1c4b-4281-b951-d872f2087c98
---
 media/filters/omx_video_decode_engine.cc      | 143 +++++++++------
 media/filters/omx_video_decode_engine.h       |   6 +-
 media/filters/video_renderer_base.cc          |  28 ++-
 media/filters/video_renderer_base.h           |   4 +
 media/omx/omx_codec_unittest.cc               |  10 +-
 media/tools/omx_test/omx_test.cc              |   2 +-
 media/tools/player_x11/gles_video_renderer.cc | 239 ++++++++++++++++++++------
 media/tools/player_x11/gles_video_renderer.h  |  15 ++
 8 files changed, 326 insertions(+), 121 deletions(-)

(limited to 'media')

diff --git a/media/filters/omx_video_decode_engine.cc b/media/filters/omx_video_decode_engine.cc
index 7f64a50..13ee05e 100644
--- a/media/filters/omx_video_decode_engine.cc
+++ b/media/filters/omx_video_decode_engine.cc
@@ -49,10 +49,13 @@ OmxVideoDecodeEngine::OmxVideoDecodeEngine()
       component_handle_(NULL),
       output_frames_allocated_(false),
       need_setup_output_port_(false) {
+  // TODO(wjia): change uses_egl_image_ to runtime setup
 #if ENABLE_EGLIMAGE == 1
   uses_egl_image_ = true;
+  DLOG(INFO) << "Uses egl image for output";
 #else
   uses_egl_image_ = false;
+  DLOG(INFO) << "Uses system memory for output";
 #endif
 }
 
@@ -113,12 +116,13 @@ void OmxVideoDecodeEngine::EmptyThisBuffer(scoped_refptr<Buffer> buffer) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
 
   if (!CanAcceptInput()) {
-    OnFeedDone(buffer);
+    FinishEmptyBuffer(buffer);
     return;
   }
 
   if (buffer->IsEndOfStream()) {
-      input_queue_has_eos_ = true;
+    DLOG(INFO) << "Input queue has EOS";
+    input_queue_has_eos_ = true;
   }
 
   // Queue this input buffer.
@@ -164,7 +168,8 @@ VideoDecodeEngine::State OmxVideoDecodeEngine::state() const {
 }
 
 void OmxVideoDecodeEngine::Stop(Callback0::Type* done_cb) {
-  DCHECK_EQ(message_loop_, MessageLoop::current());
+  // TODO(wjia): make this call in the thread.
+  // DCHECK_EQ(message_loop_, MessageLoop::current());
 
   message_loop_->PostTask(FROM_HERE,
       NewRunnableMethod(this, &OmxVideoDecodeEngine::StopTask, done_cb));
@@ -181,50 +186,48 @@ void OmxVideoDecodeEngine::OnFormatChange(
   // are already known from upper layer of the stack.
 }
 
-void OmxVideoDecodeEngine::OnFeedDone(scoped_refptr<Buffer> buffer) {
+void OmxVideoDecodeEngine::FinishEmptyBuffer(scoped_refptr<Buffer> buffer) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
 
-  empty_this_buffer_callback_->Run(buffer);
+  if (!input_queue_has_eos_)
+    empty_this_buffer_callback_->Run(buffer);
 }
 
-void OmxVideoDecodeEngine::OnReadComplete(OMX_BUFFERHEADERTYPE* buffer) {
+void OmxVideoDecodeEngine::FinishFillBuffer(OMX_BUFFERHEADERTYPE* buffer) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
 
+  scoped_refptr<VideoFrame> frame;
   // EOF
   if (!buffer) {
-    scoped_refptr<VideoFrame> frame = NULL;
+    VideoFrame::CreateEmptyFrame(&frame);
     fill_this_buffer_callback_->Run(frame);
     return;
   }
 
-  scoped_refptr<VideoFrame> frame;
   if (uses_egl_image_) {
-    frame =
-        *static_cast<scoped_refptr<VideoFrame>* >(buffer->pAppPrivate);
-    fill_this_buffer_callback_->Run(frame);
-    return;
-  }
+    frame = static_cast<VideoFrame*>(buffer->pAppPrivate);
+  } else {
+    VideoFrame::CreateFrame(GetSurfaceFormat(),
+                            width_, height_,
+                            StreamSample::kInvalidTimestamp,
+                            StreamSample::kInvalidTimestamp,
+                            &frame);
+    if (!frame.get()) {
+      // TODO(jiesun): this is also an error case handled as normal.
+      return;
+    }
 
-  VideoFrame::CreateFrame(GetSurfaceFormat(),
-                          width_, height_,
-                          StreamSample::kInvalidTimestamp,
-                          StreamSample::kInvalidTimestamp,
-                          &frame);
-  if (!frame.get()) {
-    // TODO(jiesun): this is also an error case handled as normal.
-    return;
+    // TODO(jiesun): Assume YUV 420 format.
+    const int pixels = width_ * height_;
+    memcpy(frame->data(VideoFrame::kYPlane), buffer->pBuffer, pixels);
+    memcpy(frame->data(VideoFrame::kUPlane), buffer->pBuffer + pixels,
+           pixels / 4);
+    memcpy(frame->data(VideoFrame::kVPlane),
+           buffer->pBuffer + pixels + pixels / 4,
+           pixels / 4);
   }
 
-  // TODO(jiesun): Assume YUV 420 format.
-  const int pixels = width_ * height_;
-  memcpy(frame->data(VideoFrame::kYPlane), buffer->pBuffer, pixels);
-  memcpy(frame->data(VideoFrame::kUPlane), buffer->pBuffer + pixels,
-         pixels / 4);
-  memcpy(frame->data(VideoFrame::kVPlane),
-         buffer->pBuffer + pixels + pixels /4,
-         pixels / 4);
-
-  frame->SetTimestamp(base::TimeDelta::FromMilliseconds(buffer->nTimeStamp));
+  frame->SetTimestamp(base::TimeDelta::FromMicroseconds(buffer->nTimeStamp));
   frame->SetDuration(frame->GetTimestamp() - last_pts_);
   last_pts_ = frame->GetTimestamp();
 
@@ -444,10 +447,12 @@ bool OmxVideoDecodeEngine::CreateComponent() {
     return false;
   }
 
-  // override buffer count when EGLImage is used
+  // TODO(wjia): use same buffer recycling for EGLImage and system memory.
+  // Override buffer count when EGLImage is used.
   if (uses_egl_image_) {
+    // TODO(wjia): remove hard-coded value
     port_format.nBufferCountActual = port_format.nBufferCountMin =
-       output_buffer_count_ = 3;
+       output_buffer_count_ = 4;
 
     omxresult = OMX_SetParameter(component_handle_,
                                  OMX_IndexParamPortDefinition,
@@ -470,6 +475,7 @@ void OmxVideoDecodeEngine::DoneSetStateIdle(OMX_STATETYPE state) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
   DCHECK_EQ(client_state_, kClientInitializing);
   DCHECK_EQ(OMX_StateIdle, state);
+  DLOG(INFO) << "OMX video decode engine is in Idle";
 
   il_state_ = kIlIdle;
   OnStateSetEventFunc = &OmxVideoDecodeEngine::DoneSetStateExecuting;
@@ -485,6 +491,7 @@ void OmxVideoDecodeEngine::DoneSetStateExecuting(OMX_STATETYPE state) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
   DCHECK_EQ(client_state_, kClientInitializing);
   DCHECK_EQ(OMX_StateExecuting, state);
+  DLOG(INFO) << "OMX video decode engine is in Executing";
 
   il_state_ = kIlExecuting;
   client_state_ = kClientRunning;
@@ -503,8 +510,12 @@ void OmxVideoDecodeEngine::DoneSetStateExecuting(OMX_STATETYPE state) {
 
 // Function for receiving output buffers. Hookup for buffer recycling
 // and outside allocator.
-void OmxVideoDecodeEngine::OnReceiveOneOutputBuffer(
+void OmxVideoDecodeEngine::FillThisBuffer(
     scoped_refptr<VideoFrame> video_frame) {
+  // TODO(wjia): merge buffer recycling for EGLImage and system memory path.
+  if (!video_frame.get() || VideoFrame::TYPE_EGL_IMAGE != video_frame->type())
+    return;
+
   OMX_BUFFERHEADERTYPE* omx_buffer = FindOmxBuffer(video_frame);
   if (omx_buffer) {
     if (kClientRunning == client_state_) {
@@ -524,8 +535,9 @@ void OmxVideoDecodeEngine::OnReceiveOneOutputBuffer(
       static_cast<int>(output_frames_.size()) == output_buffer_count_) {
     output_frames_allocated_ = true;
 
-    if (need_setup_output_port_)
+    if (need_setup_output_port_) {
       SetupOutputPort();
+    }
   }
 
   if (kClientError == client_state_) {
@@ -541,6 +553,12 @@ void OmxVideoDecodeEngine::OnPortSettingsChangedRun(OMX_INDEXTYPE index,
   DCHECK_EQ(client_state_, kClientRunning);
   DCHECK_EQ(port, output_port_);
 
+  // TODO(wjia): add buffer negotiation between decoder and renderer.
+  if (uses_egl_image_) {
+    DLOG(INFO) << "Port settings are changed";
+    return;
+  }
+
   if (index != OMX_IndexParamPortDefinition)
     return;
 
@@ -609,16 +627,19 @@ void OmxVideoDecodeEngine::OnPortDisableEventRun(int port) {
 
 // Enable output port and allocate buffers correspondingly
 void OmxVideoDecodeEngine::SetupOutputPort() {
-  DCHECK_EQ(client_state_, kClientRunning);
+  DCHECK_EQ(message_loop_, MessageLoop::current());
 
   need_setup_output_port_ = false;
 
-  // Enable output port.
-  output_port_state_ = kPortEnabling;
-  OnPortEnableEventFunc = &OmxVideoDecodeEngine::OnPortEnableEventRun;
-  ChangePort(OMX_CommandPortEnable, output_port_);
-  if (kClientError == client_state_) {
-    return;
+  // Enable output port when necessary since the port could be waiting for
+  // buffers, instead of port reconfiguration.
+  if (kPortEnabled != output_port_state_) {
+    output_port_state_ = kPortEnabling;
+    OnPortEnableEventFunc = &OmxVideoDecodeEngine::OnPortEnableEventRun;
+    ChangePort(OMX_CommandPortEnable, output_port_);
+    if (kClientError == client_state_) {
+      return;
+    }
   }
 
   // TODO(wjia): add state checking
@@ -633,8 +654,8 @@ void OmxVideoDecodeEngine::SetupOutputPort() {
 // Post output port enabling
 void OmxVideoDecodeEngine::OnPortEnableEventRun(int port) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
-  DCHECK_EQ(client_state_, kClientRunning);
   DCHECK_EQ(port, output_port_);
+  DCHECK_EQ(client_state_, kClientRunning);
 
   output_port_state_ = kPortEnabled;
   last_pts_ = base::TimeDelta::FromMilliseconds(0);
@@ -669,6 +690,7 @@ void OmxVideoDecodeEngine::StopTask(Callback* callback) {
 void OmxVideoDecodeEngine::DeinitFromExecuting(OMX_STATETYPE state) {
   DCHECK_EQ(state, OMX_StateExecuting);
 
+  DLOG(INFO) << "Deinit from Executing";
   OnStateSetEventFunc = &OmxVideoDecodeEngine::DeinitFromIdle;
   TransitionToState(OMX_StateIdle);
   expected_il_state_ = kIlIdle;
@@ -678,6 +700,7 @@ void OmxVideoDecodeEngine::DeinitFromIdle(OMX_STATETYPE state) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
   DCHECK_EQ(state, OMX_StateIdle);
 
+  DLOG(INFO) << "Deinit from Idle";
   il_state_ = kIlIdle;
   OnStateSetEventFunc = &OmxVideoDecodeEngine::DeinitFromLoaded;
   TransitionToState(OMX_StateLoaded);
@@ -691,6 +714,7 @@ void OmxVideoDecodeEngine::DeinitFromLoaded(OMX_STATETYPE state) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
   DCHECK_EQ(state, OMX_StateLoaded);
 
+  DLOG(INFO) << "Deinit from Loaded";
   il_state_ = kIlLoaded;
   if (component_handle_) {
     OMX_ERRORTYPE result = OMX_FreeHandle(component_handle_);
@@ -751,6 +775,12 @@ bool OmxVideoDecodeEngine::AllocateOutputBuffers() {
   DCHECK_EQ(message_loop_, MessageLoop::current());
   // DCHECK_EQ(output_buffer_count_, static_cast<int>(output_frames_.size()));
 
+  if (uses_egl_image_ && !output_frames_allocated_) {
+    DLOG(INFO) << "Output frames are not allocated yet";
+    need_setup_output_port_ = true;
+    return true;
+  }
+
   for (int i = 0; i < output_buffer_count_; ++i) {
     OMX_BUFFERHEADERTYPE* buffer;
     OMX_ERRORTYPE error;
@@ -759,7 +789,7 @@ bool OmxVideoDecodeEngine::AllocateOutputBuffers() {
       scoped_refptr<VideoFrame> video_frame = output_frame.first;
       DCHECK(!output_frame.second);
       error = OMX_UseEGLImage(component_handle_, &buffer, output_port_,
-                              &video_frame, video_frame->private_buffer());
+                              video_frame.get(), video_frame->private_buffer());
       if (error != OMX_ErrorNone)
         return false;
       output_frames_[i].second = buffer;
@@ -798,8 +828,9 @@ void OmxVideoDecodeEngine::FreeOutputBuffers() {
   if (uses_egl_image_) {
     for (size_t i = 0; i < output_frames_.size(); ++i) {
       OMX_BUFFERHEADERTYPE* omx_buffer = output_frames_[i].second;
-      if (!omx_buffer)
+      if (omx_buffer) {
         OMX_FreeBuffer(component_handle_, output_port_, omx_buffer);
+      }
     }
     output_frames_.clear();
   } else {
@@ -814,7 +845,7 @@ void OmxVideoDecodeEngine::FreeInputQueue() {
 
   while (!pending_input_queue_.empty()) {
     scoped_refptr<Buffer> buffer = pending_input_queue_.front();
-    OnFeedDone(buffer);
+    FinishEmptyBuffer(buffer);
     pending_input_queue_.pop();
   }
 }
@@ -911,13 +942,16 @@ void OmxVideoDecodeEngine::EmptyBufferTask() {
     available_input_buffers_.pop();
 
     input_has_fed_eos_ = buffer->IsEndOfStream();
+    if (input_has_fed_eos_) {
+      DLOG(INFO) << "Input has fed EOS";
+    }
 
     // setup |omx_buffer|.
     omx_buffer->pBuffer = const_cast<OMX_U8*>(buffer.get()->GetData());
     omx_buffer->nFilledLen = buffer.get()->GetDataSize();
     omx_buffer->nAllocLen = omx_buffer->nFilledLen;
     omx_buffer->nFlags |= input_has_fed_eos_ ? OMX_BUFFERFLAG_EOS : 0;
-    omx_buffer->nTimeStamp = buffer->GetTimestamp().InMilliseconds();
+    omx_buffer->nTimeStamp = buffer->GetTimestamp().InMicroseconds();
     omx_buffer->pAppPrivate = buffer.get();
 
     // Give this buffer to OMX.
@@ -940,10 +974,10 @@ void OmxVideoDecodeEngine::FulfillOneRead() {
     // If the buffer is real then send it to downstream.
     // Otherwise if it is an end-of-stream buffer then just drop it.
     if (buffer_id != kEosBuffer) {
-      OnReadComplete(output_buffers_[buffer_id]);
+      FinishFillBuffer(output_buffers_[buffer_id]);
       SendOutputBufferToComponent(output_buffers_[buffer_id]);
     } else {
-      OnReadComplete(static_cast<OMX_BUFFERHEADERTYPE*>(NULL));
+      FinishFillBuffer(static_cast<OMX_BUFFERHEADERTYPE*>(NULL));
     }
   } else if (uses_egl_image_ && !output_frames_ready_.empty()) {
     OMX_BUFFERHEADERTYPE *buffer = output_frames_ready_.front();
@@ -952,9 +986,9 @@ void OmxVideoDecodeEngine::FulfillOneRead() {
     // If the buffer is real then send it to downstream.
     // Otherwise if it is an end-of-stream buffer then just drop it.
     if (buffer->nFlags & OMX_BUFFERFLAG_EOS) {
-      OnReadComplete(static_cast<OMX_BUFFERHEADERTYPE*>(NULL));
+      FinishFillBuffer(static_cast<OMX_BUFFERHEADERTYPE*>(NULL));
     } else {
-      OnReadComplete(buffer);
+      FinishFillBuffer(buffer);
     }
   }
 }
@@ -968,7 +1002,7 @@ void OmxVideoDecodeEngine::InitialFillBuffer() {
 
   // Ask the decoder to fill the output buffers.
   if (uses_egl_image_) {
-    for (size_t i = 0; i < available_output_frames_.size(); ++i) {
+    while (!available_output_frames_.empty()) {
       OMX_BUFFERHEADERTYPE* omx_buffer = available_output_frames_.front();
       available_output_frames_.pop();
       SendOutputBufferToComponent(omx_buffer);
@@ -1068,7 +1102,8 @@ void OmxVideoDecodeEngine::EmptyBufferDoneTask(OMX_BUFFERHEADERTYPE* buffer) {
   DCHECK_EQ(message_loop_, MessageLoop::current());
 
   Buffer* stored_buffer = static_cast<Buffer*>(buffer->pAppPrivate);
-  OnFeedDone(stored_buffer);
+  buffer->pAppPrivate = NULL;
+  FinishEmptyBuffer(stored_buffer);
   stored_buffer->Release();
 
   // Enqueue the available buffer beacuse the decoder has consumed it.
@@ -1090,6 +1125,7 @@ void OmxVideoDecodeEngine::FillBufferDoneTask(OMX_BUFFERHEADERTYPE* buffer) {
   if (uses_egl_image_) {
     if (buffer->nFlags & OMX_BUFFERFLAG_EOS) {
       output_eos_ = true;
+      DLOG(INFO) << "Output has EOS";
     }
     output_frames_ready_.push(buffer);
   } else {
@@ -1108,6 +1144,7 @@ void OmxVideoDecodeEngine::FillBufferDoneTask(OMX_BUFFERHEADERTYPE* buffer) {
     if (buffer->nFlags & OMX_BUFFERFLAG_EOS || !buffer->nFilledLen) {
       buffer_id = kEosBuffer;
       output_eos_ = true;
+      DLOG(INFO) << "Output has EOS";
     }
     output_buffers_ready_.push(buffer_id);
   }
diff --git a/media/filters/omx_video_decode_engine.h b/media/filters/omx_video_decode_engine.h
index 10f1ead..c535125 100644
--- a/media/filters/omx_video_decode_engine.h
+++ b/media/filters/omx_video_decode_engine.h
@@ -43,6 +43,7 @@ class OmxVideoDecodeEngine :
                           FillThisBufferCallback* fill_buffer_callback,
                           Task* done_cb);
   virtual void EmptyThisBuffer(scoped_refptr<Buffer> buffer);
+  virtual void FillThisBuffer(scoped_refptr<VideoFrame> video_frame);
   virtual void Flush(Task* done_cb);
   virtual VideoFrame::Format GetSurfaceFormat() const;
 
@@ -90,11 +91,11 @@ class OmxVideoDecodeEngine :
   typedef Callback0::Type Callback;
 
   // calls into other classes
-  void OnFeedDone(scoped_refptr<Buffer> buffer);
+  void FinishEmptyBuffer(scoped_refptr<Buffer> buffer);
   void OnFormatChange(
       const OmxConfigurator::MediaFormat& input_format,
       const OmxConfigurator::MediaFormat& output_format);
-  void OnReadComplete(OMX_BUFFERHEADERTYPE* buffer);
+  void FinishFillBuffer(OMX_BUFFERHEADERTYPE* buffer);
   // Helper method to perform tasks when this object is stopped.
   void OnStopDone();
 
@@ -107,7 +108,6 @@ class OmxVideoDecodeEngine :
   bool CreateComponent();
   void DoneSetStateIdle(OMX_STATETYPE state);
   void DoneSetStateExecuting(OMX_STATETYPE state);
-  void OnReceiveOneOutputBuffer(scoped_refptr<VideoFrame> video_frame);
   void OnPortSettingsChangedRun(OMX_INDEXTYPE index, int port);
   void OnPortDisableEventRun(int port);
   void SetupOutputPort();
diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc
index 4833d34..c689c16 100644
--- a/media/filters/video_renderer_base.cc
+++ b/media/filters/video_renderer_base.cc
@@ -10,8 +10,8 @@
 
 namespace media {
 
-// Limit our read ahead to three frames.  One frame is typically in flux at all
-// times, as in frame n is discarded at the top of ThreadMain() while frame
+// Limit our read ahead to at least 3 frames.  One frame is typically in flux at
+// all times, as in frame n is discarded at the top of ThreadMain() while frame
 // (n + kMaxFrames) is being asynchronously fetched.  The remaining two frames
 // allow us to advance the current frame as well as read the timestamp of the
 // following frame for more accurate timing.
@@ -20,7 +20,10 @@ namespace media {
 // at the expense of memory (~0.5MB and ~1.3MB per frame for 480p and 720p
 // resolutions, respectively).  This can help on lower-end systems if there are
 // difficult sections in the movie and decoding slows down.
-static const size_t kMaxFrames = 3;
+//
+// Set to 4 because some vendor's driver doesn't allow buffer count to go below
+// preset limit, e.g., EGLImage path.
+static const size_t kMaxFrames = 4;
 
 // This equates to ~16.67 fps, which is just slow enough to be tolerable when
 // our video renderer is ahead of the audio playback.
@@ -135,6 +138,17 @@ void VideoRendererBase::Seek(base::TimeDelta time, FilterCallback* callback) {
   for (size_t i = 0; i < kMaxFrames; ++i) {
     ScheduleRead_Locked();
   }
+
+  // TODO(wjia): This would be removed if "Paint" thread allows renderer to
+  // allocate EGL images before filters are in playing state.
+  if (uses_egl_image_) {
+    state_ = kPaused;
+    VideoFrame::CreateBlackFrame(width_, height_, &current_frame_);
+    DCHECK(current_frame_);
+    OnFrameAvailable();
+    seek_callback_->Run();
+    seek_callback_.reset();
+  }
 }
 
 void VideoRendererBase::Initialize(VideoDecoder* decoder,
@@ -251,6 +265,10 @@ void VideoRendererBase::ThreadMain() {
       if (!frames_.empty() && !frames_.front()->IsEndOfStream()) {
         DCHECK_EQ(current_frame_, frames_.front());
         frames_.pop_front();
+        if (uses_egl_image_ &&
+            media::VideoFrame::TYPE_EGL_IMAGE == current_frame_->type()) {
+          decoder_->FillThisBuffer(current_frame_);
+        }
         ScheduleRead_Locked();
       }
 
@@ -269,6 +287,7 @@ void VideoRendererBase::ThreadMain() {
       // If the new front frame is end of stream, we've officially ended.
       if (frames_.front()->IsEndOfStream()) {
         state_ = kEnded;
+        LOG(INFO) << "Video render gets EOS";
         host()->NotifyEnded();
         continue;
       }
@@ -330,6 +349,9 @@ void VideoRendererBase::OnFillBufferDone(scoped_refptr<VideoFrame> frame) {
 
   // Enqueue the frame.
   frames_.push_back(frame);
+  if (uses_egl_image_ &&
+      media::VideoFrame::TYPE_EGL_IMAGE != current_frame_->type())
+    current_frame_ = frame;
   DCHECK_LE(frames_.size(), kMaxFrames);
   frame_available_.Signal();
 
diff --git a/media/filters/video_renderer_base.h b/media/filters/video_renderer_base.h
index f66d1cd..551d234 100644
--- a/media/filters/video_renderer_base.h
+++ b/media/filters/video_renderer_base.h
@@ -81,6 +81,10 @@ class VideoRendererBase : public VideoRenderer,
   // class executes on.
   virtual void OnFrameAvailable() = 0;
 
+  virtual VideoDecoder* GetDecoder() {
+    return decoder_.get();
+  }
+
  private:
   // Callback from video decoder to deliver decoded video frames and decrements
   // |pending_reads_|.
diff --git a/media/omx/omx_codec_unittest.cc b/media/omx/omx_codec_unittest.cc
index ebec9c9..92201d4 100644
--- a/media/omx/omx_codec_unittest.cc
+++ b/media/omx/omx_codec_unittest.cc
@@ -307,7 +307,7 @@ class OmxCodecTest : public testing::Test {
 
   void FillBufferDoneCallback(scoped_refptr<VideoFrame> frame) {
     output_units_.push_back(frame);
-    if (frame.get() == NULL)
+    if (frame->IsEndOfStream())
       got_eos_ = true;
   }
 
@@ -414,9 +414,9 @@ TEST_F(OmxCodecTest, NormalFlow) {
 
   // Send EndOfStream, expect eos flag.
   SendEOSInputBuffer();
-  EXPECT_EQ(kBufferCount, static_cast<int>(input_units_.size()));
+  EXPECT_EQ(kBufferCount - 1, static_cast<int>(input_units_.size()));
   EXPECT_EQ(1, static_cast<int>(output_units_.size()));
-  EXPECT_EQ(count-1, static_cast<int>(output_pool_.size()));
+  EXPECT_EQ(count - 1, static_cast<int>(output_pool_.size()));
   EXPECT_EQ(true, got_eos_);
 
   // Shutdown.
@@ -468,9 +468,9 @@ TEST_F(OmxCodecTest, RecycleInputBuffers) {
 
   // Send EndOfStream, expect eos flag.
   SendEOSInputBuffer();
-  EXPECT_EQ(kBufferCount, static_cast<int>(input_units_.size()));
+  EXPECT_EQ(kBufferCount - 1, static_cast<int>(input_units_.size()));
   EXPECT_EQ(1, static_cast<int>(output_units_.size()));
-  EXPECT_EQ(count-1, static_cast<int>(output_pool_.size()));
+  EXPECT_EQ(count - 1, static_cast<int>(output_pool_.size()));
   EXPECT_EQ(true, got_eos_);
 
   // Shutdown.
diff --git a/media/tools/omx_test/omx_test.cc b/media/tools/omx_test/omx_test.cc
index 560e6d6..2deddeb 100644
--- a/media/tools/omx_test/omx_test.cc
+++ b/media/tools/omx_test/omx_test.cc
@@ -127,7 +127,7 @@ class TestApp : public base::RefCountedThreadSafe<TestApp> {
       first_sample_delivered_time_ = base::TimeTicks::HighResNow();
 
     // If we are readding to the end, then stop.
-    if (frame.get() == NULL) {
+    if (frame->IsEndOfStream()) {
       engine_->Stop(NewCallback(this, &TestApp::StopCallback));
       return;
     }
diff --git a/media/tools/player_x11/gles_video_renderer.cc b/media/tools/player_x11/gles_video_renderer.cc
index 85e4329..a275366 100644
--- a/media/tools/player_x11/gles_video_renderer.cc
+++ b/media/tools/player_x11/gles_video_renderer.cc
@@ -5,6 +5,7 @@
 #include "media/tools/player_x11/gles_video_renderer.h"
 
 #include <dlfcn.h>
+#include <EGL/eglext.h>
 #include <X11/Xutil.h>
 #include <X11/extensions/Xrender.h>
 #include <X11/extensions/Xcomposite.h>
@@ -18,7 +19,9 @@
 GlesVideoRenderer* GlesVideoRenderer::instance_ = NULL;
 
 GlesVideoRenderer::GlesVideoRenderer(Display* display, Window window)
-    : display_(display),
+    : egl_create_image_khr_(NULL),
+      egl_destroy_image_khr_(NULL),
+      display_(display),
       window_(window),
       new_frame_(false),
       egl_display_(NULL),
@@ -43,6 +46,14 @@ void GlesVideoRenderer::OnStop() {
   // calls may fail. Need to fix them.
   eglMakeCurrent(egl_display_, EGL_NO_SURFACE,
                  EGL_NO_SURFACE, EGL_NO_CONTEXT);
+  for (size_t i = 0; i < egl_frames_.size(); ++i) {
+    scoped_refptr<media::VideoFrame> frame = egl_frames_[i].first;
+    if (frame->private_buffer())
+      egl_destroy_image_khr_(egl_display_, frame->private_buffer());
+    if (egl_frames_[i].second)
+      glDeleteTextures(1, &egl_frames_[i].second);
+  }
+  egl_frames_.clear();
   eglDestroyContext(egl_display_, egl_context_);
   eglDestroySurface(egl_display_, egl_surface_);
 }
@@ -70,6 +81,14 @@ static const float kTextureCoords[8] = {
   1, 1,
 };
 
+// Texture Coordinates mapping the entire texture for EGL image.
+static const float kTextureCoordsEgl[8] = {
+  0, 1,
+  0, 0,
+  1, 1,
+  1, 0,
+};
+
 // Pass-through vertex shader.
 static const char kVertexShader[] =
     "precision highp float; precision highp int;\n"
@@ -103,6 +122,16 @@ static const char kFragmentShader[] =
     "  gl_FragColor = vec4(rgb, 1);\n"
     "}\n";
 
+// Color shader for EGLImage.
+static const char kFragmentShaderEgl[] =
+    "varying vec2 interp_tc;\n"
+    "\n"
+    "uniform sampler2D tex;\n"
+    "\n"
+    "void main() {\n"
+    "  gl_FragColor = texture2D(tex, interp_tc);\n"
+    "}\n";
+
 // Buffer size for compile errors.
 static const unsigned int kErrorSize = 4096;
 
@@ -149,8 +178,23 @@ void GlesVideoRenderer::Paint() {
   scoped_refptr<media::VideoFrame> video_frame;
   GetCurrentFrame(&video_frame);
 
-  if (!video_frame)
+  if (!video_frame.get()) {
     return;
+  }
+
+  if (uses_egl_image_) {
+    if (media::VideoFrame::TYPE_EGL_IMAGE == video_frame->type()) {
+
+      GLuint texture = FindTexture(video_frame);
+      if (texture) {
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, texture);
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+        eglSwapBuffers(egl_display_, egl_surface_);
+      }
+    }
+    return;
+  }
 
   // Convert YUV frame to RGB.
   DCHECK(video_frame->format() == media::VideoFrame::YV12 ||
@@ -200,6 +244,17 @@ void GlesVideoRenderer::Paint() {
   eglSwapBuffers(egl_display_, egl_surface_);
 }
 
+// find if texture exists corresponding to video_frame
+GLuint GlesVideoRenderer::FindTexture(
+    scoped_refptr<media::VideoFrame> video_frame) {
+  for (size_t i = 0; i < egl_frames_.size(); ++i) {
+    scoped_refptr<media::VideoFrame> frame = egl_frames_[i].first;
+    if (video_frame->private_buffer() == frame->private_buffer())
+      return egl_frames_[i].second;
+  }
+  return NULL;
+}
+
 bool GlesVideoRenderer::InitializeGles() {
   // Resize the window to fit that of the video.
   XResizeWindow(display_, window_, width_, height_);
@@ -288,6 +343,127 @@ bool GlesVideoRenderer::InitializeGles() {
   eglQuerySurface(egl_display_, egl_surface_, EGL_HEIGHT, &height);
   glViewport(0, 0, width_, height_);
 
+  if (uses_egl_image_) {
+    CreateTextureAndProgramEgl();
+    return true;
+  }
+
+  CreateTextureAndProgramYuv2Rgb();
+
+  // We are getting called on a thread. Release the context so that it can be
+  // made current on the main thread.
+  // TODO(hclam): Fix this if neccessary. Currently the following call fails
+  // for some drivers.
+  // eglMakeCurrent(egl_display_, EGL_NO_SURFACE,
+  //                EGL_NO_SURFACE, EGL_NO_CONTEXT);
+  return true;
+}
+
+void GlesVideoRenderer::CreateShader(GLuint program,
+                                     GLenum type,
+                                     const char* source,
+                                     int size) {
+  GLuint shader = glCreateShader(type);
+  glShaderSource(shader, 1, &source, &size);
+  glCompileShader(shader);
+  int result = GL_FALSE;
+  glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
+  if (!result) {
+    char log[kErrorSize];
+    int len;
+    glGetShaderInfoLog(shader, kErrorSize - 1, &len, log);
+    log[kErrorSize - 1] = 0;
+    LOG(FATAL) << log;
+  }
+  glAttachShader(program, shader);
+  glDeleteShader(shader);
+}
+
+void GlesVideoRenderer::LinkProgram(GLuint program) {
+  glLinkProgram(program);
+  int result = GL_FALSE;
+  glGetProgramiv(program, GL_LINK_STATUS, &result);
+  if (!result) {
+    char log[kErrorSize];
+    int len;
+    glGetProgramInfoLog(program, kErrorSize - 1, &len, log);
+    log[kErrorSize - 1] = 0;
+    LOG(FATAL) << log;
+  }
+  glUseProgram(program);
+  glDeleteProgram(program);
+}
+
+void GlesVideoRenderer::CreateTextureAndProgramEgl() {
+  if (!egl_create_image_khr_)
+    egl_create_image_khr_ = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>
+                            (eglGetProcAddress("eglCreateImageKHR"));
+  if (!egl_destroy_image_khr_)
+    egl_destroy_image_khr_ = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>
+                             (eglGetProcAddress("eglDestroyImageKHR"));
+  // TODO(wjia): get count from decoder.
+  for (int i = 0; i < 4; i++) {
+    GLuint texture;
+    EGLint attrib = EGL_NONE;
+    EGLImageKHR egl_image;
+
+    glGenTextures(1, &texture);
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexImage2D(
+        GL_TEXTURE_2D,
+        0,
+        GL_RGBA,
+        width_,
+        height_,
+        0,
+        GL_RGBA,
+        GL_UNSIGNED_BYTE,
+        NULL);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    egl_image = egl_create_image_khr_(
+        egl_display_,
+        egl_context_,
+        EGL_GL_TEXTURE_2D_KHR,
+        reinterpret_cast<EGLClientBuffer>(texture),
+        &attrib);
+
+    scoped_refptr<media::VideoFrame> video_frame;
+    const base::TimeDelta kZero;
+    media::VideoFrame:: CreatePrivateFrame(
+        media::VideoFrame::TYPE_EGL_IMAGE,
+        media::VideoFrame::RGB565,
+        width_, height_, kZero, kZero,
+        egl_image,
+        &video_frame);
+    egl_frames_.push_back(std::make_pair(video_frame, texture));
+    GetDecoder()->FillThisBuffer(video_frame);
+  }
+
+  GLuint program = glCreateProgram();
+
+  // Create shader for EGL image
+  CreateShader(program, GL_VERTEX_SHADER,
+               kVertexShader, sizeof(kVertexShader));
+  CreateShader(program, GL_FRAGMENT_SHADER,
+               kFragmentShaderEgl, sizeof(kFragmentShaderEgl));
+  LinkProgram(program);
+
+  // Bind parameters.
+  glUniform1i(glGetUniformLocation(program, "tex"), 0);
+
+  int pos_location = glGetAttribLocation(program, "in_pos");
+  glEnableVertexAttribArray(pos_location);
+  glVertexAttribPointer(pos_location, 2, GL_FLOAT, GL_FALSE, 0, kVertices);
+
+  int tc_location = glGetAttribLocation(program, "in_tc");
+  glEnableVertexAttribArray(tc_location);
+  glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0,
+                        kTextureCoordsEgl);
+}
+
+void GlesVideoRenderer::CreateTextureAndProgramYuv2Rgb() {
   // Create 3 textures, one for each plane, and bind them to different
   // texture units.
   glGenTextures(media::VideoFrame::kNumYUVPlanes, textures_);
@@ -313,52 +489,11 @@ bool GlesVideoRenderer::InitializeGles() {
   GLuint program = glCreateProgram();
 
   // Create our YUV->RGB shader.
-  GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
-  const char* vs_source = kVertexShader;
-  int vs_size = sizeof(kVertexShader);
-  glShaderSource(vertex_shader, 1, &vs_source, &vs_size);
-  glCompileShader(vertex_shader);
-  int result = GL_FALSE;
-  glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &result);
-  if (!result) {
-    char log[kErrorSize];
-    int len;
-    glGetShaderInfoLog(vertex_shader, kErrorSize - 1, &len, log);
-    log[kErrorSize - 1] = 0;
-    LOG(FATAL) << log;
-  }
-  glAttachShader(program, vertex_shader);
-  glDeleteShader(vertex_shader);
-
-  GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
-  const char* ps_source = kFragmentShader;
-  int ps_size = sizeof(kFragmentShader);
-  glShaderSource(fragment_shader, 1, &ps_source, &ps_size);
-  glCompileShader(fragment_shader);
-  result = GL_FALSE;
-  glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &result);
-  if (!result) {
-    char log[kErrorSize];
-    int len;
-    glGetShaderInfoLog(fragment_shader, kErrorSize - 1, &len, log);
-    log[kErrorSize - 1] = 0;
-    LOG(FATAL) << log;
-  }
-  glAttachShader(program, fragment_shader);
-  glDeleteShader(fragment_shader);
-
-  glLinkProgram(program);
-  result = GL_FALSE;
-  glGetProgramiv(program, GL_LINK_STATUS, &result);
-  if (!result) {
-    char log[kErrorSize];
-    int len;
-    glGetProgramInfoLog(program, kErrorSize - 1, &len, log);
-    log[kErrorSize - 1] = 0;
-    LOG(FATAL) << log;
-  }
-  glUseProgram(program);
-  glDeleteProgram(program);
+  CreateShader(program, GL_VERTEX_SHADER,
+               kVertexShader, sizeof(kVertexShader));
+  CreateShader(program, GL_FRAGMENT_SHADER,
+               kFragmentShaderEgl, sizeof(kFragmentShader));
+  LinkProgram(program);
 
   // Bind parameters.
   glUniform1i(glGetUniformLocation(program, "y_tex"), 0);
@@ -377,12 +512,4 @@ bool GlesVideoRenderer::InitializeGles() {
   glEnableVertexAttribArray(tc_location);
   glVertexAttribPointer(tc_location, 2, GL_FLOAT, GL_FALSE, 0,
                         kTextureCoords);
-
-  // We are getting called on a thread. Release the context so that it can be
-  // made current on the main thread.
-  // TODO(hclam): Fix this if neccessary. Currently the following call fails
-  // for some drivers.
-  // eglMakeCurrent(egl_display_, EGL_NO_SURFACE,
-  //                EGL_NO_SURFACE, EGL_NO_CONTEXT);
-  return true;
 }
diff --git a/media/tools/player_x11/gles_video_renderer.h b/media/tools/player_x11/gles_video_renderer.h
index 39478ac..075bdbc 100644
--- a/media/tools/player_x11/gles_video_renderer.h
+++ b/media/tools/player_x11/gles_video_renderer.h
@@ -6,12 +6,14 @@
 #define MEDIA_TOOLS_PLAYER_X11_GL_VIDEO_RENDERER_H_
 
 #include <EGL/egl.h>
+#include <EGL/eglext.h>
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
 #include "base/lock.h"
 #include "base/scoped_ptr.h"
 #include "media/base/factory.h"
+#include "media/base/video_frame.h"
 #include "media/filters/video_renderer_base.h"
 
 class GlesVideoRenderer : public media::VideoRendererBase {
@@ -44,7 +46,16 @@ class GlesVideoRenderer : public media::VideoRendererBase {
   friend class scoped_refptr<GlesVideoRenderer>;
   virtual ~GlesVideoRenderer();
 
+  GLuint FindTexture(scoped_refptr<media::VideoFrame> video_frame);
   bool InitializeGles();
+  void CreateShader(GLuint program, GLenum type,
+                    const char* vs_source, int vs_size);
+  void LinkProgram(GLuint program);
+  void CreateTextureAndProgramEgl();
+  void CreateTextureAndProgramYuv2Rgb();
+
+  PFNEGLCREATEIMAGEKHRPROC egl_create_image_khr_;
+  PFNEGLDESTROYIMAGEKHRPROC egl_destroy_image_khr_;
 
   int width_;
   int height_;
@@ -62,6 +73,10 @@ class GlesVideoRenderer : public media::VideoRendererBase {
   EGLSurface egl_surface_;
   EGLContext egl_context_;
 
+  // textures for EGL image
+  typedef std::pair<scoped_refptr<media::VideoFrame>, GLuint> EglFrame;
+  std::vector<EglFrame> egl_frames_;
+
   // 3 textures, one for each plane.
   GLuint textures_[3];
 
-- 
cgit v1.1