diff options
author | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-07 18:59:36 +0000 |
---|---|---|
committer | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-07 18:59:36 +0000 |
commit | 3b098bbef42fa93a2c17c96c888a65564191f767 (patch) | |
tree | 0509385a390e0ff9b348e96382c10074127319f6 /chrome/renderer | |
parent | c7b7800afbf7aadb5c9f99c95209237cdf869678 (diff) | |
download | chromium_src-3b098bbef42fa93a2c17c96c888a65564191f767.zip chromium_src-3b098bbef42fa93a2c17c96c888a65564191f767.tar.gz chromium_src-3b098bbef42fa93a2c17c96c888a65564191f767.tar.bz2 |
Implement GpuVideoDecoderHost and unit tests
Add the following feature to GpuVideoDecoderHost:
1. Video frame allocation / release.
2. ProduceVideoFrame / ConsumeVideoFrame using frames allocated.
3. Change GpuVideoDecoder creation to asynchronous.
BUG=53714
TEST=unit_tests --gtest_filter=GpuVideoDecoder*
Review URL: http://codereview.chromium.org/3397027
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@61824 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/renderer')
-rw-r--r-- | chrome/renderer/gpu_video_decoder_host.cc | 298 | ||||
-rw-r--r-- | chrome/renderer/gpu_video_decoder_host.h | 77 | ||||
-rw-r--r-- | chrome/renderer/gpu_video_decoder_host_unittest.cc | 259 | ||||
-rw-r--r-- | chrome/renderer/gpu_video_service_host.cc | 18 | ||||
-rw-r--r-- | chrome/renderer/gpu_video_service_host.h | 10 |
5 files changed, 541 insertions, 121 deletions
diff --git a/chrome/renderer/gpu_video_decoder_host.cc b/chrome/renderer/gpu_video_decoder_host.cc index 08e7d5d..4a97702 100644 --- a/chrome/renderer/gpu_video_decoder_host.cc +++ b/chrome/renderer/gpu_video_decoder_host.cc @@ -5,22 +5,25 @@ #include "chrome/renderer/gpu_video_decoder_host.h" #include "chrome/common/gpu_messages.h" -#include "chrome/renderer/gpu_video_service_host.h" -#include "chrome/renderer/render_thread.h" +#include "chrome/common/message_router.h" +#include "media/video/video_decode_context.h" -GpuVideoDecoderHost::GpuVideoDecoderHost(GpuVideoServiceHost* service_host, +GpuVideoDecoderHost::GpuVideoDecoderHost(MessageRouter* router, IPC::Message::Sender* ipc_sender, - int context_route_id) - : gpu_video_service_host_(service_host), + int context_route_id, + int32 decoder_host_id) + : router_(router), ipc_sender_(ipc_sender), context_route_id_(context_route_id), + message_loop_(NULL), event_handler_(NULL), - buffer_id_serial_(0), + context_(NULL), state_(kStateUninitialized), - input_buffer_busy_(false) { + decoder_host_id_(decoder_host_id), + decoder_id_(0), + input_buffer_busy_(false), + current_frame_id_(0) { memset(&config_, 0, sizeof(config_)); - memset(&done_param_, 0, sizeof(done_param_)); - memset(&decoder_info_, 0, sizeof(decoder_info_)); } void GpuVideoDecoderHost::OnChannelError() { @@ -29,6 +32,8 @@ void GpuVideoDecoderHost::OnChannelError() { void GpuVideoDecoderHost::OnMessageReceived(const IPC::Message& msg) { IPC_BEGIN_MESSAGE_MAP(GpuVideoDecoderHost, msg) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_CreateVideoDecoderDone, + OnCreateVideoDecoderDone) IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_InitializeACK, OnInitializeDone) IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_DestroyACK, @@ -38,7 +43,13 @@ void GpuVideoDecoderHost::OnMessageReceived(const IPC::Message& msg) { IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_EmptyThisBufferACK, OnEmptyThisBufferACK) IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_EmptyThisBufferDone, - OnEmptyThisBufferDone) + OnProduceVideoSample) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_ConsumeVideoFrame, + OnConsumeVideoFrame) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_AllocateVideoFrames, + OnAllocateVideoFrames) + IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_ReleaseAllVideoFrames, + OnReleaseAllVideoFrames) IPC_MESSAGE_UNHANDLED_ERROR() IPC_END_MESSAGE_MAP() } @@ -46,41 +57,44 @@ void GpuVideoDecoderHost::OnMessageReceived(const IPC::Message& msg) { void GpuVideoDecoderHost::Initialize( MessageLoop* message_loop, VideoDecodeEngine::EventHandler* event_handler, media::VideoDecodeContext* context, const media::VideoCodecConfig& config) { - // TODO(hclam): Call |event_handler| here. - DCHECK_EQ(state_, kStateUninitialized); - - // Save the event handler before we perform initialization operations so - // that we can report initialization events. - event_handler_ = event_handler; - - // TODO(hclam): This create video decoder operation is synchronous, need to - // make it asynchronous. - decoder_info_.context_id = context_route_id_; - if (!ipc_sender_->Send( - new GpuChannelMsg_CreateVideoDecoder(&decoder_info_))) { - LOG(ERROR) << "GpuChannelMsg_CreateVideoDecoder failed"; + if (MessageLoop::current() != message_loop) { + message_loop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &GpuVideoDecoderHost::Initialize, message_loop, + event_handler, context, config)); return; } - // Add the route so we'll receive messages. - gpu_video_service_host_->AddRoute(my_route_id(), this); - - // Save the configuration parameters. + // Initialization operations should be performed on the message loop assigned. + // Save the parameters and post a task to complete initialization. + DCHECK_EQ(kStateUninitialized, state_); + DCHECK(!message_loop_); + message_loop_ = message_loop; + event_handler_ = event_handler; + context_ = context; config_ = config; - // TODO(hclam): Initialize |param| with the right values. - GpuVideoDecoderInitParam param; - param.width = config.width; - param.height = config.height; + // Add the route so we'll receive messages. + router_->AddRoute(decoder_host_id_, this); - if (!ipc_sender_ || !ipc_sender_->Send( - new GpuVideoDecoderMsg_Initialize(route_id(), param))) { - LOG(ERROR) << "GpuVideoDecoderMsg_Initialize failed"; + if (!ipc_sender_->Send( + new GpuChannelMsg_CreateVideoDecoder(context_route_id_, + decoder_host_id_))) { + LOG(ERROR) << "GpuChannelMsg_CreateVideoDecoder failed"; + event_handler_->OnError(); return; } } void GpuVideoDecoderHost::ConsumeVideoSample(scoped_refptr<Buffer> buffer) { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + this, &GpuVideoDecoderHost::ConsumeVideoSample, buffer)); + return; + } + DCHECK_NE(state_, kStateUninitialized); DCHECK_NE(state_, kStateFlushing); @@ -90,124 +104,226 @@ void GpuVideoDecoderHost::ConsumeVideoSample(scoped_refptr<Buffer> buffer) { return; input_buffer_queue_.push_back(buffer); - SendInputBufferToGpu(); + SendConsumeVideoSample(); } void GpuVideoDecoderHost::ProduceVideoFrame(scoped_refptr<VideoFrame> frame) { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + this, &GpuVideoDecoderHost::ProduceVideoFrame, frame)); + return; + } + DCHECK_NE(state_, kStateUninitialized); - // Depends on who provides buffer. client could return buffer to - // us while flushing. + // During flush client of this object will call this method to return all + // video frames. We should only ignore such method calls if we are in error + // state. if (state_ == kStateError) return; - // TODO(hclam): We should keep an IDMap to convert between a frame a buffer - // ID so that we can signal GpuVideoDecoder in GPU process to use the buffer. - // This eliminates one conversion step. + // Check that video frame is valid. + if (!frame || frame->format() == media::VideoFrame::EMPTY || + frame->IsEndOfStream()) { + return; + } + + SendProduceVideoFrame(frame); } void GpuVideoDecoderHost::Uninitialize() { - if (!ipc_sender_ || !ipc_sender_->Send( - new GpuVideoDecoderMsg_Destroy(route_id()))) { - LOG(ERROR) << "GpuVideoDecoderMsg_Destroy failed"; + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &GpuVideoDecoderHost::Uninitialize)); return; } - gpu_video_service_host_->RemoveRoute(my_route_id()); - return; + if (!ipc_sender_->Send(new GpuVideoDecoderMsg_Destroy(decoder_id_))) { + LOG(ERROR) << "GpuVideoDecoderMsg_Destroy failed"; + event_handler_->OnError(); + } } void GpuVideoDecoderHost::Flush() { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, NewRunnableMethod(this, &GpuVideoDecoderHost::Flush)); + return; + } + state_ = kStateFlushing; - if (!ipc_sender_ || !ipc_sender_->Send( - new GpuVideoDecoderMsg_Flush(route_id()))) { + if (!ipc_sender_->Send(new GpuVideoDecoderMsg_Flush(decoder_id_))) { LOG(ERROR) << "GpuVideoDecoderMsg_Flush failed"; + event_handler_->OnError(); return; } + input_buffer_queue_.clear(); // TODO(jiesun): because GpuVideoDeocder/GpuVideoDecoder are asynchronously. // We need a way to make flush logic more clear. but I think ring buffer // should make the busy flag obsolete, therefore I will leave it for now. input_buffer_busy_ = false; - return; } void GpuVideoDecoderHost::Seek() { // TODO(hclam): Implement. } +void GpuVideoDecoderHost::OnCreateVideoDecoderDone(int32 decoder_id) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + decoder_id_ = decoder_id; + + // TODO(hclam): Initialize |param| with the right values. + GpuVideoDecoderInitParam param; + param.width = config_.width; + param.height = config_.height; + + if (!ipc_sender_->Send( + new GpuVideoDecoderMsg_Initialize(decoder_id, param))) { + LOG(ERROR) << "GpuVideoDecoderMsg_Initialize failed"; + event_handler_->OnError(); + } +} + void GpuVideoDecoderHost::OnInitializeDone( const GpuVideoDecoderInitDoneParam& param) { - done_param_ = param; - bool success = false; + DCHECK_EQ(message_loop_, MessageLoop::current()); - do { - if (!param.success) - break; + bool success = param.success && + base::SharedMemory::IsHandleValid(param.input_buffer_handle); - if (!base::SharedMemory::IsHandleValid(param.input_buffer_handle)) - break; + if (success) { input_transfer_buffer_.reset( new base::SharedMemory(param.input_buffer_handle, false)); - if (!input_transfer_buffer_->Map(param.input_buffer_size)) - break; - - success = true; - } while (0); - + success = input_transfer_buffer_->Map(param.input_buffer_size); + } state_ = success ? kStateNormal : kStateError; - media::VideoCodecInfo info; - info.success = success; // TODO(hclam): There's too many unnecessary copies for width and height! // Need to clean it up. // TODO(hclam): Need to fill in more information. + media::VideoCodecInfo info; + info.success = success; info.stream_info.surface_width = config_.width; info.stream_info.surface_height = config_.height; event_handler_->OnInitializeComplete(info); } void GpuVideoDecoderHost::OnUninitializeDone() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + input_transfer_buffer_.reset(); + router_->RemoveRoute(decoder_host_id_); + context_->ReleaseAllVideoFrames(); event_handler_->OnUninitializeComplete(); } void GpuVideoDecoderHost::OnFlushDone() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + state_ = kStateNormal; event_handler_->OnFlushComplete(); } -void GpuVideoDecoderHost::OnEmptyThisBufferDone() { +void GpuVideoDecoderHost::OnEmptyThisBufferACK() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + input_buffer_busy_ = false; + SendConsumeVideoSample(); +} + +void GpuVideoDecoderHost::OnProduceVideoSample() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + DCHECK_EQ(kStateNormal, state_); + event_handler_->ProduceVideoSample(NULL); } void GpuVideoDecoderHost::OnConsumeVideoFrame(int32 frame_id, int64 timestamp, int64 duration, int32 flags) { - scoped_refptr<VideoFrame> frame; + DCHECK_EQ(message_loop_, MessageLoop::current()); + scoped_refptr<VideoFrame> frame; if (flags & kGpuVideoEndOfStream) { VideoFrame::CreateEmptyFrame(&frame); } else { - // TODO(hclam): Use |frame_id| to find the VideoFrame. + frame = video_frame_map_[frame_id]; + DCHECK(frame) << "Invalid frame ID received"; + + frame->SetDuration(base::TimeDelta::FromMilliseconds(duration)); + frame->SetTimestamp(base::TimeDelta::FromMilliseconds(timestamp)); } - // TODO(hclam): Call the event handler. event_handler_->ConsumeVideoFrame(frame); } -void GpuVideoDecoderHost::OnEmptyThisBufferACK() { - input_buffer_busy_ = false; - SendInputBufferToGpu(); +void GpuVideoDecoderHost::OnAllocateVideoFrames( + int32 n, uint32 width, uint32 height, int32 format) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + DCHECK_EQ(0u, video_frames_.size()); + + context_->AllocateVideoFrames( + n, width, height, static_cast<media::VideoFrame::Format>(format), + &video_frames_, + NewRunnableMethod(this, + &GpuVideoDecoderHost::OnAllocateVideoFramesDone)); +} + +void GpuVideoDecoderHost::OnReleaseAllVideoFrames() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + context_->ReleaseAllVideoFrames(); + video_frame_map_.clear(); + video_frames_.clear(); +} + +void GpuVideoDecoderHost::OnAllocateVideoFramesDone() { + if (MessageLoop::current() != message_loop_) { + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod( + this, &GpuVideoDecoderHost::OnAllocateVideoFramesDone)); + return; + } + + // After video frame allocation is done we add these frames to a map and + // send them to the GPU process. + DCHECK(video_frames_.size()) << "No video frames allocated"; + for (size_t i = 0; i < video_frames_.size(); ++i) { + DCHECK(video_frames_[i]); + video_frame_map_.insert( + std::make_pair(current_frame_id_, video_frames_[i])); + SendVideoFrameAllocated(current_frame_id_, video_frames_[i]); + ++current_frame_id_; + } +} + +void GpuVideoDecoderHost::SendVideoFrameAllocated( + int32 frame_id, scoped_refptr<media::VideoFrame> frame) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + std::vector<uint32> textures; + for (size_t i = 0; i < frame->planes(); ++i) { + textures.push_back(frame->gl_texture(i)); + } + + if (!ipc_sender_->Send(new GpuVideoDecoderMsg_VideoFrameAllocated( + decoder_id_, frame_id, textures))) { + LOG(ERROR) << "GpuVideoDecoderMsg_EmptyThisBuffer failed"; + } } -void GpuVideoDecoderHost::SendInputBufferToGpu() { - if (input_buffer_busy_) return; - if (input_buffer_queue_.empty()) return; +void GpuVideoDecoderHost::SendConsumeVideoSample() { + DCHECK_EQ(message_loop_, MessageLoop::current()); + if (input_buffer_busy_ || input_buffer_queue_.empty()) + return; input_buffer_busy_ = true; - scoped_refptr<Buffer> buffer; - buffer = input_buffer_queue_.front(); + scoped_refptr<Buffer> buffer = input_buffer_queue_.front(); input_buffer_queue_.pop_front(); // Send input data to GPU process. @@ -216,8 +332,36 @@ void GpuVideoDecoderHost::SendInputBufferToGpu() { param.size = buffer->GetDataSize(); param.timestamp = buffer->GetTimestamp().InMicroseconds(); memcpy(input_transfer_buffer_->memory(), buffer->GetData(), param.size); - if (!ipc_sender_ || !ipc_sender_->Send( - new GpuVideoDecoderMsg_EmptyThisBuffer(route_id(), param))) { + + if (!ipc_sender_->Send( + new GpuVideoDecoderMsg_EmptyThisBuffer(decoder_id_, param))) { LOG(ERROR) << "GpuVideoDecoderMsg_EmptyThisBuffer failed"; } } + +void GpuVideoDecoderHost::SendProduceVideoFrame( + scoped_refptr<media::VideoFrame> frame) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + // TODO(hclam): I should mark a frame being used to DCHECK and make sure + // user doesn't use it the second time. + // TODO(hclam): Derive a faster way to lookup the frame ID. + bool found = false; + int32 frame_id = 0; + for (VideoFrameMap::iterator i = video_frame_map_.begin(); + i != video_frame_map_.end(); ++i) { + if (frame == i->second) { + frame_id = i->first; + found = true; + break; + } + } + + DCHECK(found) << "Invalid video frame received"; + if (found && !ipc_sender_->Send( + new GpuVideoDecoderMsg_ProduceVideoFrame(decoder_id_, frame_id))) { + LOG(ERROR) << "GpuVideoDecoderMsg_ProduceVideoFrame failed"; + } +} + +DISABLE_RUNNABLE_METHOD_REFCOUNT(GpuVideoDecoderHost); diff --git a/chrome/renderer/gpu_video_decoder_host.h b/chrome/renderer/gpu_video_decoder_host.h index 68953f6..e5a26fd 100644 --- a/chrome/renderer/gpu_video_decoder_host.h +++ b/chrome/renderer/gpu_video_decoder_host.h @@ -6,6 +6,7 @@ #define CHROME_RENDERER_GPU_VIDEO_DECODER_HOST_H_ #include <deque> +#include <map> #include "base/singleton.h" #include "chrome/common/gpu_video_common.h" @@ -18,7 +19,7 @@ using media::VideoFrame; using media::Buffer; -class GpuVideoServiceHost; +class MessageRouter; // This class is used to talk to GpuVideoDecoder in the GPU process through // IPC messages. It implements the interface of VideoDecodeEngine so users @@ -36,6 +37,10 @@ class GpuVideoServiceHost; class GpuVideoDecoderHost : public media::VideoDecodeEngine, public IPC::Channel::Listener { public: + GpuVideoDecoderHost(MessageRouter* router, + IPC::Message::Sender* ipc_sender, + int context_route_id, + int32 decoder_host_id); virtual ~GpuVideoDecoderHost() {} // IPC::Channel::Listener. @@ -55,7 +60,7 @@ class GpuVideoDecoderHost : public media::VideoDecodeEngine, virtual void Seek(); private: - friend class GpuVideoServiceHost; + typedef std::map<int32, scoped_refptr<media::VideoFrame> > VideoFrameMap; // Internal states. enum GpuVideoDecoderHostState { @@ -65,30 +70,38 @@ class GpuVideoDecoderHost : public media::VideoDecodeEngine, kStateFlushing, }; - // Private constructor. - GpuVideoDecoderHost(GpuVideoServiceHost* service_host, - IPC::Message::Sender* ipc_sender, - int context_route_id); - - // Input message handler. + // Handlers for messages received from the GPU process. + void OnCreateVideoDecoderDone(int32 decoder_id); void OnInitializeDone(const GpuVideoDecoderInitDoneParam& param); void OnUninitializeDone(); void OnFlushDone(); - void OnEmptyThisBufferDone(); + void OnEmptyThisBufferACK(); + void OnProduceVideoSample(); void OnConsumeVideoFrame(int32 frame_id, int64 timestamp, int64 duration, int32 flags); - void OnEmptyThisBufferACK(); + void OnAllocateVideoFrames(int32 n, uint32 width, + uint32 height, int32 format); + void OnReleaseAllVideoFrames(); + + // Handler for VideoDecodeContext. This method is called when video frames + // allocation is done. + void OnAllocateVideoFramesDone(); - // Helper function. - void SendInputBufferToGpu(); + // Send a message to the GPU process to inform that a video frame is + // allocated. + void SendVideoFrameAllocated(int32 frame_id, + scoped_refptr<media::VideoFrame> frame); - // Getter methods for IDs. - int32 decoder_id() { return decoder_info_.decoder_id; } - int32 route_id() { return decoder_info_.decoder_route_id; } - int32 my_route_id() { return decoder_info_.decoder_host_route_id; } + // Send a video sample to the GPU process and tell it to use the buffer for + // video decoding. + void SendConsumeVideoSample(); - // We expect that GpuVideoServiceHost's always available during our life span. - GpuVideoServiceHost* gpu_video_service_host_; + // Look up the frame_id for |frame| and send a message to the GPU process + // to use that video frame to produce an output. + void SendProduceVideoFrame(scoped_refptr<media::VideoFrame> frame); + + // A router used to send us IPC messages. + MessageRouter* router_; // Sends IPC messages to the GPU process. IPC::Message::Sender* ipc_sender_; @@ -96,24 +109,27 @@ class GpuVideoDecoderHost : public media::VideoDecodeEngine, // Route ID of the GLES2 context in the GPU process. int context_route_id_; + // Message loop that this object runs on. + MessageLoop* message_loop_; + // We expect that the client of us will always available during our life span. EventHandler* event_handler_; - // Globally identify this decoder in the GPU process. - GpuVideoDecoderInfoParam decoder_info_; - - // Input buffer id serial number generator. - int32 buffer_id_serial_; + // A Context for allocating video frame textures. + media::VideoDecodeContext* context_; // Hold information about GpuVideoDecoder configuration. media::VideoCodecConfig config_; - // Hold information about output surface format, etc. - GpuVideoDecoderInitDoneParam done_param_; - // Current state of video decoder. GpuVideoDecoderHostState state_; + // ID of this GpuVideoDecoderHost. + int32 decoder_host_id_; + + // ID of GpuVideoDecoder in the GPU process. + int32 decoder_id_; + // We are not able to push all received buffer to gpu process at once. std::deque<scoped_refptr<Buffer> > input_buffer_queue_; @@ -125,6 +141,15 @@ class GpuVideoDecoderHost : public media::VideoDecodeEngine, // TODO(jiesun): remove output buffer when hardware composition is ready. scoped_ptr<base::SharedMemory> input_transfer_buffer_; + // Frame ID for the newly generated video frame. + int32 current_frame_id_; + + // The list of video frames allocated by VideoDecodeContext. + std::vector<scoped_refptr<media::VideoFrame> > video_frames_; + + // The mapping between video frame ID and a video frame. + VideoFrameMap video_frame_map_; + DISALLOW_COPY_AND_ASSIGN(GpuVideoDecoderHost); }; diff --git a/chrome/renderer/gpu_video_decoder_host_unittest.cc b/chrome/renderer/gpu_video_decoder_host_unittest.cc new file mode 100644 index 0000000..c0a10be --- /dev/null +++ b/chrome/renderer/gpu_video_decoder_host_unittest.cc @@ -0,0 +1,259 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop.h" +#include "chrome/common/gpu_messages.h" +#include "chrome/common/message_router.h" +#include "chrome/renderer/gpu_video_decoder_host.h" +#include "media/video/mock_objects.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::DoAll; +using testing::NotNull; +using testing::Return; +using testing::SetArgumentPointee; + +static const int kContextRouteId = 50; +static const int kDecoderHostId = 51; +static const int kDecoderId = 51; +static const int kVideoFrames = 3; +static const int kWidth = 320; +static const int kHeight = 240; +static const int kTransportBufferSize = 1024; + +ACTION_P(SimulateAllocateVideoFrames, frames) { + // Fake some texture IDs here. + media::VideoFrame::GlTexture textures[] = {4, 5, 6}; + for (int i = 0; i < kVideoFrames; ++i) { + scoped_refptr<media::VideoFrame> frame; + media::VideoFrame::CreateFrameGlTexture(media::VideoFrame::YV12, + kWidth, kHeight, textures, + base::TimeDelta(), + base::TimeDelta(), + &frame); + frames->push_back(frame); + arg4->push_back(frame); + } + + // Execute the callback to complete the task. + arg5->Run(); + delete arg5; +} + +ACTION_P2(SendMessage, handler, msg) { + handler->OnMessageReceived(msg); +} + +class GpuVideoDecoderHostTest : public testing::Test, + public IPC::Message::Sender, + public media::VideoDecodeEngine::EventHandler { + public: + // This method is used to dispatch IPC messages to mock methods. + virtual bool Send(IPC::Message* msg) { + EXPECT_TRUE(msg); + if (!msg) + return false; + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(GpuVideoDecoderHostTest, *msg) + IPC_MESSAGE_HANDLER(GpuChannelMsg_CreateVideoDecoder, + OnCreateVideoDecoder) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_Initialize, + OnInitialize) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_Destroy, + OnDestroy) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_Flush, + OnFlush) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_EmptyThisBuffer, + OnEmptyThisBuffer) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_VideoFrameAllocated, + OnVideoFrameAllocated) + IPC_MESSAGE_HANDLER(GpuVideoDecoderMsg_ProduceVideoFrame, + OnProduceVideoFrame) + IPC_MESSAGE_UNHANDLED_ERROR() + IPC_END_MESSAGE_MAP() + EXPECT_TRUE(handled); + delete msg; + return true; + } + + // Mock methods for outgoing messages. + MOCK_METHOD1(OnInitialize, void(GpuVideoDecoderInitParam param)); + MOCK_METHOD0(OnDestroy, void()); + MOCK_METHOD0(OnFlush, void()); + MOCK_METHOD1(OnEmptyThisBuffer, + void(GpuVideoDecoderInputBufferParam param)); + MOCK_METHOD1(OnProduceVideoFrame, void(int32 frame_id)); + MOCK_METHOD2(OnVideoFrameAllocated, + void(int32 frame_id, std::vector<uint32> textures)); + MOCK_METHOD2(OnCreateVideoDecoder, + void(int32 context_route_id, int32 decoder_host_id)); + + // Mock methods for VideoDecodeEngine::EventHandler. + MOCK_METHOD1(ProduceVideoSample, + void(scoped_refptr<media::Buffer> buffer)); + MOCK_METHOD1(ConsumeVideoFrame, + void(scoped_refptr<media::VideoFrame> frame)); + MOCK_METHOD1(OnInitializeComplete, + void(const media::VideoCodecInfo& info)); + MOCK_METHOD0(OnUninitializeComplete, void()); + MOCK_METHOD0(OnFlushComplete, void()); + MOCK_METHOD0(OnSeekComplete, void()); + MOCK_METHOD0(OnError, void()); + MOCK_METHOD1(OnFormatChange, + void(media::VideoStreamInfo stream_info)); + + void Initialize() { + decoder_host_.reset( + new GpuVideoDecoderHost(&router_, this, kContextRouteId, + kDecoderHostId)); + shared_memory_.reset(new base::SharedMemory()); + shared_memory_->Create("", false, false, kTransportBufferSize); + + GpuVideoDecoderHostMsg_CreateVideoDecoderDone msg1(kDecoderHostId, + kDecoderId); + EXPECT_CALL(*this, OnCreateVideoDecoder(kContextRouteId, kDecoderHostId)) + .WillOnce(SendMessage(decoder_host_.get(), msg1)); + + GpuVideoDecoderInitDoneParam param; + param.success = true; + param.input_buffer_size = kTransportBufferSize; + param.input_buffer_handle = shared_memory_->handle(); + + GpuVideoDecoderHostMsg_InitializeACK msg2(kDecoderHostId, param); + EXPECT_CALL(*this, OnInitialize(_)) + .WillOnce(SendMessage(decoder_host_.get(), msg2)); + EXPECT_CALL(*this, OnInitializeComplete(_)); + + media::VideoCodecConfig config; + config.codec = media::kCodecH264; + config.width = kWidth; + config.height = kHeight; + decoder_host_->Initialize(&message_loop_, this, &context_, config); + message_loop_.RunAllPending(); + } + + void Uninitialize() { + // A message is sent to GPU process to destroy the decoder. + GpuVideoDecoderHostMsg_DestroyACK msg(kDecoderHostId); + EXPECT_CALL(*this, OnDestroy()) + .WillOnce(SendMessage(decoder_host_.get(), msg)); + EXPECT_CALL(context_, ReleaseAllVideoFrames()); + EXPECT_CALL(*this, OnUninitializeComplete()); + decoder_host_->Uninitialize(); + } + + void AllocateVideoFrames() { + // Expect context is called to allocate video frames. + EXPECT_CALL(context_, + AllocateVideoFrames(kVideoFrames, kWidth, kHeight, + media::VideoFrame::YV12, + NotNull(), NotNull())) + .WillOnce(SimulateAllocateVideoFrames(&frames_)) + .RetiresOnSaturation(); + + // Expect that we send the video frames to the GPU process. + EXPECT_CALL(*this, OnVideoFrameAllocated(_, _)) + .Times(kVideoFrames) + .RetiresOnSaturation(); + + // Pretend that a message is sent to GpuVideoDecoderHost to allocate + // video frames. + GpuVideoDecoderHostMsg_AllocateVideoFrames msg( + kDecoderHostId, kVideoFrames, kWidth, kHeight, + static_cast<int32>(media::VideoFrame::YV12)); + decoder_host_->OnMessageReceived(msg); + } + + void ReleaseVideoFrames() { + // Expect that context is called to release all video frames. + EXPECT_CALL(context_, ReleaseAllVideoFrames()) + .RetiresOnSaturation(); + + // Pretend a message is sent to release all video frames. + GpuVideoDecoderHostMsg_ReleaseAllVideoFrames msg(kDecoderHostId); + decoder_host_->OnMessageReceived(msg); + + // Clear the list of video frames allocated. + frames_.clear(); + } + + void ProduceVideoFrame(int first_frame_id) { + for (int i = 0; i < kVideoFrames; ++i) { + // Expect that a request is received to produce a video frame. + GpuVideoDecoderHostMsg_ConsumeVideoFrame msg( + kDecoderHostId, first_frame_id + i, 0, 0, 0); + EXPECT_CALL(*this, OnProduceVideoFrame(first_frame_id + i)) + .WillOnce(SendMessage(decoder_host_.get(), msg)) + .RetiresOnSaturation(); + + // Expect that a reply is made when a video frame is ready. + EXPECT_CALL(*this, ConsumeVideoFrame(frames_[i])) + .RetiresOnSaturation(); + + // Use the allocated video frames to make a request. + decoder_host_->ProduceVideoFrame(frames_[i]); + } + } + + private: + MessageLoop message_loop_; + MessageRouter router_; + media::MockVideoDecodeContext context_; + + scoped_ptr<GpuVideoDecoderHost> decoder_host_; + scoped_ptr<base::SharedMemory> shared_memory_; + + // Keeps the video frames allocated. + std::vector<scoped_refptr<media::VideoFrame> > frames_; +}; + +// Test that when we initialize GpuVideoDecoderHost the corresponding +// IPC messages are sent and at the end OnInitializeComplete() is +// called with the right parameters. +TEST_F(GpuVideoDecoderHostTest, Initialize) { + Initialize(); +} + +// Test that the sequence of method calls and IPC messages is correct. +// And at the end OnUninitializeComplete() is called. +TEST_F(GpuVideoDecoderHostTest, Uninitialize) { + Initialize(); + Uninitialize(); +} + +// Test that IPC messages are sent to GpuVideoDecoderHost and it +// calls VideoDecodeContext to allocate textures and send these +// textures back to the GPU process by IPC messages. +TEST_F(GpuVideoDecoderHostTest, AllocateVideoFrames) { + Initialize(); + AllocateVideoFrames(); + Uninitialize(); +} + +// Test that IPC messages are sent to GpuVideoDecoderHost to +// release textures and VideoDecodeContext is called correctly. +TEST_F(GpuVideoDecoderHostTest, ReleaseVideoFrames) { + Initialize(); + AllocateVideoFrames(); + ReleaseVideoFrames(); + AllocateVideoFrames(); + ReleaseVideoFrames(); + Uninitialize(); +} + +// Test the sequence of IPC messages and methods calls for a decode +// routine. This tests the output port only. +TEST_F(GpuVideoDecoderHostTest, ProduceVideoFrame) { + Initialize(); + AllocateVideoFrames(); + ProduceVideoFrame(0); + ReleaseVideoFrames(); + AllocateVideoFrames(); + ProduceVideoFrame(kVideoFrames); + ReleaseVideoFrames(); + Uninitialize(); +} diff --git a/chrome/renderer/gpu_video_service_host.cc b/chrome/renderer/gpu_video_service_host.cc index 94803a4..d47e309 100644 --- a/chrome/renderer/gpu_video_service_host.cc +++ b/chrome/renderer/gpu_video_service_host.cc @@ -11,7 +11,8 @@ GpuVideoServiceHost::GpuVideoServiceHost() : channel_host_(NULL), router_(NULL), - message_loop_(NULL) { + message_loop_(NULL), + next_decoder_host_id_(0) { memset(&service_info_, 0, sizeof(service_info_)); } @@ -55,14 +56,9 @@ GpuVideoDecoderHost* GpuVideoServiceHost::CreateVideoDecoder( int context_route_id) { DCHECK(RenderThread::current()); - return new GpuVideoDecoderHost(this, channel_host_, context_route_id); -} - -void GpuVideoServiceHost::AddRoute(int route_id, - GpuVideoDecoderHost* decoder_host) { - router_->AddRoute(route_id, decoder_host); -} - -void GpuVideoServiceHost::RemoveRoute(int route_id) { - router_->RemoveRoute(route_id); + GpuVideoDecoderHost* host = new GpuVideoDecoderHost(router_, channel_host_, + context_route_id, + next_decoder_host_id_); + ++next_decoder_host_id_; + return host; } diff --git a/chrome/renderer/gpu_video_service_host.h b/chrome/renderer/gpu_video_service_host.h index e5417a4..ff54cd2 100644 --- a/chrome/renderer/gpu_video_service_host.h +++ b/chrome/renderer/gpu_video_service_host.h @@ -45,13 +45,6 @@ class GpuVideoServiceHost : public IPC::Channel::Listener, // Returns a GpuVideoDecoderHost as a handle to control the video decoder. GpuVideoDecoderHost* CreateVideoDecoder(int context_route_id); - // TODO(hclam): Provide a method to destroy the decoder. We also need to - // listen to context lost event. - - // Methods called by GpuVideoDecoderHost. - void AddRoute(int route_id, GpuVideoDecoderHost* decoder_host); - void RemoveRoute(int route_id); - private: GpuVideoServiceHost(); @@ -60,6 +53,9 @@ class GpuVideoServiceHost : public IPC::Channel::Listener, GpuVideoServiceInfoParam service_info_; MessageLoop* message_loop_; // Message loop of render thread. + // ID for the next GpuVideoDecoderHost. + int32 next_decoder_host_id_; + friend struct DefaultSingletonTraits<GpuVideoServiceHost>; DISALLOW_COPY_AND_ASSIGN(GpuVideoServiceHost); }; |