blob: 416c22b681256752c78a4fa88fa7a68e8693ce5e [file] [log] [blame]
// Copyright (c) 2011 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 "content/renderer/gpu_video_decoder_host.h"
#include "content/common/gpu/gpu_messages.h"
#include "content/common/message_router.h"
#include "media/base/pipeline.h"
#include "media/video/video_decode_context.h"
GpuVideoDecoderHost::GpuVideoDecoderHost(MessageRouter* router,
IPC::Message::Sender* ipc_sender,
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),
context_(NULL),
width_(0),
height_(0),
state_(kStateUninitialized),
decoder_host_id_(decoder_host_id),
decoder_id_(0),
input_buffer_busy_(false),
current_frame_id_(0) {
}
GpuVideoDecoderHost::~GpuVideoDecoderHost() {}
void GpuVideoDecoderHost::OnChannelError() {
ipc_sender_ = NULL;
}
bool GpuVideoDecoderHost::OnMessageReceived(const IPC::Message& msg) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(GpuVideoDecoderHost, msg)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_CreateVideoDecoderDone,
OnCreateVideoDecoderDone)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_InitializeACK,
OnInitializeDone)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_DestroyACK,
OnUninitializeDone)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_FlushACK,
OnFlushDone)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_PrerollDone,
OnPrerollDone)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_EmptyThisBufferACK,
OnEmptyThisBufferACK)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_EmptyThisBufferDone,
OnProduceVideoSample)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_ConsumeVideoFrame,
OnConsumeVideoFrame)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_AllocateVideoFrames,
OnAllocateVideoFrames)
IPC_MESSAGE_HANDLER(GpuVideoDecoderHostMsg_ReleaseAllVideoFrames,
OnReleaseAllVideoFrames)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
DCHECK(handled);
return handled;
}
void GpuVideoDecoderHost::Initialize(
MessageLoop* message_loop,
VideoDecodeEngine::EventHandler* event_handler,
media::VideoDecodeContext* context,
const media::VideoDecoderConfig& config) {
DCHECK_EQ(kStateUninitialized, state_);
DCHECK(!message_loop_);
message_loop_ = message_loop;
event_handler_ = event_handler;
context_ = context;
width_ = config.width();
height_ = config.height();
if (MessageLoop::current() != message_loop) {
message_loop->PostTask(
FROM_HERE,
NewRunnableMethod(this, &GpuVideoDecoderHost::CreateVideoDecoder));
return;
}
CreateVideoDecoder();
}
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);
// We never own input buffers, therefore when client in flush state, it
// never call us with EmptyThisBuffer.
if (state_ != kStateNormal)
return;
input_buffer_queue_.push_back(buffer);
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);
// 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;
// Check that video frame is valid.
if (!frame || frame->format() == media::VideoFrame::EMPTY ||
frame->IsEndOfStream()) {
return;
}
SendProduceVideoFrame(frame);
}
void GpuVideoDecoderHost::Uninitialize() {
if (MessageLoop::current() != message_loop_) {
message_loop_->PostTask(
FROM_HERE,
NewRunnableMethod(this, &GpuVideoDecoderHost::Uninitialize));
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_->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;
}
void GpuVideoDecoderHost::Seek() {
if (MessageLoop::current() != message_loop_) {
message_loop_->PostTask(
FROM_HERE, NewRunnableMethod(this, &GpuVideoDecoderHost::Seek));
return;
}
if (!ipc_sender_->Send(new GpuVideoDecoderMsg_Preroll(decoder_id_))) {
LOG(ERROR) << "GpuVideoDecoderMsg_Preroll failed";
event_handler_->OnError();
return;
}
}
void GpuVideoDecoderHost::CreateVideoDecoder() {
DCHECK_EQ(message_loop_, MessageLoop::current());
// Add the route so we'll receive messages.
router_->AddRoute(decoder_host_id_, this);
if (!ipc_sender_->Send(
new GpuChannelMsg_CreateVideoDecoder(context_route_id_,
decoder_host_id_))) {
LOG(ERROR) << "GpuChannelMsg_CreateVideoDecoder failed";
event_handler_->OnError();
return;
}
}
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 = width_;
param.height = 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) {
DCHECK_EQ(message_loop_, MessageLoop::current());
bool success = param.success &&
base::SharedMemory::IsHandleValid(param.input_buffer_handle);
if (success) {
input_transfer_buffer_.reset(
new base::SharedMemory(param.input_buffer_handle, false));
success = input_transfer_buffer_->Map(param.input_buffer_size);
}
state_ = success ? kStateNormal : kStateError;
// 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 = width_;
info.stream_info.surface_height = 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::OnPrerollDone() {
DCHECK_EQ(message_loop_, MessageLoop::current());
state_ = kStateNormal;
event_handler_->OnSeekComplete();
}
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) {
DCHECK_EQ(message_loop_, MessageLoop::current());
scoped_refptr<VideoFrame> frame;
if (flags & kGpuVideoEndOfStream) {
VideoFrame::CreateEmptyFrame(&frame);
} else {
frame = video_frame_map_[frame_id];
DCHECK(frame) << "Invalid frame ID received";
frame->SetDuration(base::TimeDelta::FromMicroseconds(duration));
frame->SetTimestamp(base::TimeDelta::FromMicroseconds(timestamp));
}
media::PipelineStatistics statistics;
// TODO(sjl): Fill in statistics.
event_handler_->ConsumeVideoFrame(frame, statistics);
}
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::SendConsumeVideoSample() {
DCHECK_EQ(message_loop_, MessageLoop::current());
if (input_buffer_busy_ || input_buffer_queue_.empty())
return;
input_buffer_busy_ = true;
scoped_refptr<Buffer> buffer = input_buffer_queue_.front();
input_buffer_queue_.pop_front();
// Send input data to GPU process.
GpuVideoDecoderInputBufferParam param;
param.offset = 0;
param.size = buffer->GetDataSize();
param.timestamp = buffer->GetTimestamp().InMicroseconds();
memcpy(input_transfer_buffer_->memory(), buffer->GetData(), param.size);
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);