blob: fbc24d82cb20c5b4597b903ca214f2d6042c2faf [file] [log] [blame]
// Copyright 2014 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 "media/cast/sender/external_video_encoder.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_vector.h"
#include "base/memory/shared_memory.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/cast/cast_defines.h"
#include "media/cast/logging/logging_defines.h"
#include "media/cast/net/cast_transport_config.h"
#include "media/video/video_encode_accelerator.h"
namespace media {
namespace cast {
class LocalVideoEncodeAcceleratorClient;
} // namespace cast
} // namespace media
namespace {
static const size_t kOutputBufferCount = 3;
void LogFrameEncodedEvent(
const scoped_refptr<media::cast::CastEnvironment>& cast_environment,
base::TimeTicks event_time,
media::cast::RtpTimestamp rtp_timestamp,
uint32 frame_id) {
cast_environment->Logging()->InsertFrameEvent(
event_time, media::cast::FRAME_ENCODED, media::cast::VIDEO_EVENT,
rtp_timestamp, frame_id);
}
} // namespace
namespace media {
namespace cast {
// Container for the associated data of a video frame being processed.
struct EncodedFrameReturnData {
EncodedFrameReturnData(base::TimeTicks c_time,
VideoEncoder::FrameEncodedCallback callback) {
capture_time = c_time;
frame_encoded_callback = callback;
}
base::TimeTicks capture_time;
VideoEncoder::FrameEncodedCallback frame_encoded_callback;
};
// The ExternalVideoEncoder class can be deleted directly by cast, while
// LocalVideoEncodeAcceleratorClient stays around long enough to properly shut
// down the VideoEncodeAccelerator.
class LocalVideoEncodeAcceleratorClient
: public VideoEncodeAccelerator::Client,
public base::RefCountedThreadSafe<LocalVideoEncodeAcceleratorClient> {
public:
// Create an instance of this class and post a task to create
// video_encode_accelerator_. A ref to |this| will be kept, awaiting reply
// via ProxyCreateVideoEncodeAccelerator, which will provide us with the
// encoder task runner and vea instance. We cannot be destroyed until we
// receive the reply, otherwise the VEA object created may leak.
static scoped_refptr<LocalVideoEncodeAcceleratorClient> Create(
scoped_refptr<CastEnvironment> cast_environment,
const CreateVideoEncodeAcceleratorCallback& create_vea_cb,
const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb,
const base::WeakPtr<ExternalVideoEncoder>& weak_owner) {
scoped_refptr<LocalVideoEncodeAcceleratorClient> client(
new LocalVideoEncodeAcceleratorClient(
cast_environment, create_video_encode_mem_cb, weak_owner));
// This will keep a ref to |client|, if weak_owner is destroyed before
// ProxyCreateVideoEncodeAccelerator is called, we will stay alive until
// we can properly destroy the VEA.
create_vea_cb.Run(base::Bind(
&LocalVideoEncodeAcceleratorClient::OnCreateVideoEncodeAcceleratorProxy,
client));
return client;
}
// Initialize the real HW encoder.
void Initialize(const VideoSenderConfig& video_config) {
DCHECK(encoder_task_runner_);
DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
VideoCodecProfile output_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN;
switch (video_config.codec) {
case CODEC_VIDEO_VP8:
output_profile = media::VP8PROFILE_ANY;
break;
case CODEC_VIDEO_H264:
output_profile = media::H264PROFILE_MAIN;
break;
case CODEC_VIDEO_FAKE:
NOTREACHED() << "Fake software video encoder cannot be external";
break;
default:
NOTREACHED() << "Video codec not specified or not supported";
break;
}
max_frame_rate_ = video_config.max_frame_rate;
bool result = video_encode_accelerator_->Initialize(
media::VideoFrame::I420,
gfx::Size(video_config.width, video_config.height),
output_profile,
video_config.start_bitrate,
this);
UMA_HISTOGRAM_BOOLEAN("Cast.Sender.VideoEncodeAcceleratorInitializeSuccess",
result);
if (!result) {
NotifyError(VideoEncodeAccelerator::kInvalidArgumentError);
return;
}
// Wait until shared memory is allocated to indicate that encoder is
// initialized.
}
// Destroy the VEA on the correct thread.
void Destroy() {
DCHECK(encoder_task_runner_);
if (!video_encode_accelerator_)
return;
if (encoder_task_runner_->RunsTasksOnCurrentThread()) {
video_encode_accelerator_.reset();
} else {
// We do this instead of just reposting to encoder_task_runner_, because
// we are called from the destructor.
encoder_task_runner_->PostTask(
FROM_HERE,
base::Bind(&DestroyVideoEncodeAcceleratorOnEncoderThread,
base::Passed(&video_encode_accelerator_)));
}
}
void SetBitRate(uint32 bit_rate) {
DCHECK(encoder_task_runner_);
DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
video_encode_accelerator_->RequestEncodingParametersChange(bit_rate,
max_frame_rate_);
}
void EncodeVideoFrame(
const scoped_refptr<media::VideoFrame>& video_frame,
const base::TimeTicks& capture_time,
bool key_frame_requested,
const VideoEncoder::FrameEncodedCallback& frame_encoded_callback) {
DCHECK(encoder_task_runner_);
DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
encoded_frame_data_storage_.push_back(
EncodedFrameReturnData(capture_time, frame_encoded_callback));
// BitstreamBufferReady will be called once the encoder is done.
video_encode_accelerator_->Encode(video_frame, key_frame_requested);
}
protected:
virtual void NotifyError(VideoEncodeAccelerator::Error error) OVERRIDE {
DCHECK(encoder_task_runner_);
DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
VLOG(1) << "ExternalVideoEncoder NotifyError: " << error;
cast_environment_->PostTask(
CastEnvironment::MAIN,
FROM_HERE,
base::Bind(&ExternalVideoEncoder::EncoderError, weak_owner_));
}
// Called to allocate the input and output buffers.
virtual void RequireBitstreamBuffers(unsigned int input_count,
const gfx::Size& input_coded_size,
size_t output_buffer_size) OVERRIDE {
DCHECK(encoder_task_runner_);
DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
DCHECK(video_encode_accelerator_);
for (size_t j = 0; j < kOutputBufferCount; ++j) {
create_video_encode_memory_cb_.Run(
output_buffer_size,
base::Bind(&LocalVideoEncodeAcceleratorClient::OnCreateSharedMemory,
this));
}
}
// Encoder has encoded a frame and it's available in one of out output
// buffers.
virtual void BitstreamBufferReady(int32 bitstream_buffer_id,
size_t payload_size,
bool key_frame) OVERRIDE {
DCHECK(encoder_task_runner_);
DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
if (bitstream_buffer_id < 0 ||
bitstream_buffer_id >= static_cast<int32>(output_buffers_.size())) {
NOTREACHED();
VLOG(1) << "BitstreamBufferReady(): invalid bitstream_buffer_id="
<< bitstream_buffer_id;
NotifyError(media::VideoEncodeAccelerator::kPlatformFailureError);
return;
}
base::SharedMemory* output_buffer = output_buffers_[bitstream_buffer_id];
if (payload_size > output_buffer->mapped_size()) {
NOTREACHED();
VLOG(1) << "BitstreamBufferReady(): invalid payload_size = "
<< payload_size;
NotifyError(media::VideoEncodeAccelerator::kPlatformFailureError);
return;
}
if (key_frame)
key_frame_encountered_ = true;
if (!key_frame_encountered_) {
// Do not send video until we have encountered the first key frame.
// Save the bitstream buffer in |stream_header_| to be sent later along
// with the first key frame.
stream_header_.append(static_cast<const char*>(output_buffer->memory()),
payload_size);
} else if (!encoded_frame_data_storage_.empty()) {
scoped_ptr<EncodedFrame> encoded_frame(
new EncodedFrame());
encoded_frame->dependency = key_frame ? EncodedFrame::KEY :
EncodedFrame::DEPENDENT;
encoded_frame->frame_id = ++last_encoded_frame_id_;
if (key_frame)
encoded_frame->referenced_frame_id = encoded_frame->frame_id;
else
encoded_frame->referenced_frame_id = encoded_frame->frame_id - 1;
encoded_frame->reference_time =
encoded_frame_data_storage_.front().capture_time;
encoded_frame->rtp_timestamp =
GetVideoRtpTimestamp(encoded_frame->reference_time);
if (!stream_header_.empty()) {
encoded_frame->data = stream_header_;
stream_header_.clear();
}
encoded_frame->data.append(
static_cast<const char*>(output_buffer->memory()), payload_size);
cast_environment_->PostTask(
CastEnvironment::MAIN,
FROM_HERE,
base::Bind(&LogFrameEncodedEvent,
cast_environment_,
cast_environment_->Clock()->NowTicks(),
encoded_frame->rtp_timestamp,
encoded_frame->frame_id));
cast_environment_->PostTask(
CastEnvironment::MAIN,
FROM_HERE,
base::Bind(encoded_frame_data_storage_.front().frame_encoded_callback,
base::Passed(&encoded_frame)));
encoded_frame_data_storage_.pop_front();
} else {
VLOG(1) << "BitstreamBufferReady(): no encoded frame data available";
}
// We need to re-add the output buffer to the encoder after we are done
// with it.
video_encode_accelerator_->UseOutputBitstreamBuffer(media::BitstreamBuffer(
bitstream_buffer_id,
output_buffers_[bitstream_buffer_id]->handle(),
output_buffers_[bitstream_buffer_id]->mapped_size()));
}
private:
LocalVideoEncodeAcceleratorClient(
scoped_refptr<CastEnvironment> cast_environment,
const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb,
const base::WeakPtr<ExternalVideoEncoder>& weak_owner)
: cast_environment_(cast_environment),
create_video_encode_memory_cb_(create_video_encode_mem_cb),
weak_owner_(weak_owner),
last_encoded_frame_id_(kStartFrameId),
key_frame_encountered_(false) {}
// Trampoline VEA creation callback to OnCreateVideoEncodeAccelerator()
// on encoder_task_runner. Normally we would just repost the same method to
// it, and would not need a separate proxy method, but we can't
// ThreadTaskRunnerHandle::Get() in unittests just yet.
void OnCreateVideoEncodeAcceleratorProxy(
scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
scoped_ptr<media::VideoEncodeAccelerator> vea) {
encoder_task_runner->PostTask(
FROM_HERE,
base::Bind(&media::cast::LocalVideoEncodeAcceleratorClient::
OnCreateVideoEncodeAccelerator,
this,
encoder_task_runner,
base::Passed(&vea)));
}
void OnCreateVideoEncodeAccelerator(
scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner,
scoped_ptr<media::VideoEncodeAccelerator> vea) {
encoder_task_runner_ = encoder_task_runner;
video_encode_accelerator_.reset(vea.release());
cast_environment_->PostTask(
CastEnvironment::MAIN,
FROM_HERE,
base::Bind(&ExternalVideoEncoder::OnCreateVideoEncodeAccelerator,
weak_owner_,
encoder_task_runner_));
}
// Note: This method can be called on any thread.
void OnCreateSharedMemory(scoped_ptr<base::SharedMemory> memory) {
encoder_task_runner_->PostTask(
FROM_HERE,
base::Bind(&LocalVideoEncodeAcceleratorClient::ReceivedSharedMemory,
this,
base::Passed(&memory)));
}
void ReceivedSharedMemory(scoped_ptr<base::SharedMemory> memory) {
DCHECK(encoder_task_runner_);
DCHECK(encoder_task_runner_->RunsTasksOnCurrentThread());
output_buffers_.push_back(memory.release());
// Wait until all requested buffers are received.
if (output_buffers_.size() < kOutputBufferCount)
return;
// Immediately provide all output buffers to the VEA.
for (size_t i = 0; i < output_buffers_.size(); ++i) {
video_encode_accelerator_->UseOutputBitstreamBuffer(
media::BitstreamBuffer(static_cast<int32>(i),
output_buffers_[i]->handle(),
output_buffers_[i]->mapped_size()));
}
cast_environment_->PostTask(
CastEnvironment::MAIN,
FROM_HERE,
base::Bind(&ExternalVideoEncoder::EncoderInitialized, weak_owner_));
}
static void DestroyVideoEncodeAcceleratorOnEncoderThread(
scoped_ptr<media::VideoEncodeAccelerator> vea) {
// VEA::~VEA specialization takes care of calling Destroy() on the VEA impl.
}
friend class base::RefCountedThreadSafe<LocalVideoEncodeAcceleratorClient>;
virtual ~LocalVideoEncodeAcceleratorClient() {
Destroy();
DCHECK(!video_encode_accelerator_);
}
const scoped_refptr<CastEnvironment> cast_environment_;
scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner_;
scoped_ptr<media::VideoEncodeAccelerator> video_encode_accelerator_;
const CreateVideoEncodeMemoryCallback create_video_encode_memory_cb_;
const base::WeakPtr<ExternalVideoEncoder> weak_owner_;
int max_frame_rate_;
uint32 last_encoded_frame_id_;
bool key_frame_encountered_;
std::string stream_header_;
// Shared memory buffers for output with the VideoAccelerator.
ScopedVector<base::SharedMemory> output_buffers_;
// FIFO list.
std::list<EncodedFrameReturnData> encoded_frame_data_storage_;
DISALLOW_COPY_AND_ASSIGN(LocalVideoEncodeAcceleratorClient);
};
ExternalVideoEncoder::ExternalVideoEncoder(
scoped_refptr<CastEnvironment> cast_environment,
const VideoSenderConfig& video_config,
const CreateVideoEncodeAcceleratorCallback& create_vea_cb,
const CreateVideoEncodeMemoryCallback& create_video_encode_mem_cb)
: video_config_(video_config),
cast_environment_(cast_environment),
encoder_active_(false),
key_frame_requested_(false),
weak_factory_(this) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
video_accelerator_client_ =
LocalVideoEncodeAcceleratorClient::Create(cast_environment_,
create_vea_cb,
create_video_encode_mem_cb,
weak_factory_.GetWeakPtr());
DCHECK(video_accelerator_client_);
}
ExternalVideoEncoder::~ExternalVideoEncoder() {
}
void ExternalVideoEncoder::EncoderInitialized() {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
encoder_active_ = true;
}
void ExternalVideoEncoder::EncoderError() {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
encoder_active_ = false;
}
void ExternalVideoEncoder::OnCreateVideoEncodeAccelerator(
scoped_refptr<base::SingleThreadTaskRunner> encoder_task_runner) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
encoder_task_runner_ = encoder_task_runner;
encoder_task_runner_->PostTask(
FROM_HERE,
base::Bind(&LocalVideoEncodeAcceleratorClient::Initialize,
video_accelerator_client_,
video_config_));
}
bool ExternalVideoEncoder::EncodeVideoFrame(
const scoped_refptr<media::VideoFrame>& video_frame,
const base::TimeTicks& capture_time,
const FrameEncodedCallback& frame_encoded_callback) {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
if (!encoder_active_)
return false;
encoder_task_runner_->PostTask(
FROM_HERE,
base::Bind(&LocalVideoEncodeAcceleratorClient::EncodeVideoFrame,
video_accelerator_client_,
video_frame,
capture_time,
key_frame_requested_,
frame_encoded_callback));
key_frame_requested_ = false;
return true;
}
// Inform the encoder about the new target bit rate.
void ExternalVideoEncoder::SetBitRate(int new_bit_rate) {
if (!encoder_active_) {
// If we receive SetBitRate() before VEA creation callback is invoked,
// cache the new bit rate in the encoder config and use the new settings
// to initialize VEA.
video_config_.start_bitrate = new_bit_rate;
return;
}
encoder_task_runner_->PostTask(
FROM_HERE,
base::Bind(&LocalVideoEncodeAcceleratorClient::SetBitRate,
video_accelerator_client_,
new_bit_rate));
}
// Inform the encoder to encode the next frame as a key frame.
void ExternalVideoEncoder::GenerateKeyFrame() {
DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
key_frame_requested_ = true;
}
// Inform the encoder to only reference frames older or equal to frame_id;
void ExternalVideoEncoder::LatestFrameIdToReference(uint32 /*frame_id*/) {
// Do nothing not supported.
}
} // namespace cast
} // namespace media