Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2170)

Unified Diff: media/cast/video_sender/external_video_encoder.cc

Issue 116623002: Cast: Adding support for GPU accelerated encode (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Removed dependency on content Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/cast/video_sender/external_video_encoder.cc
diff --git a/media/cast/video_sender/external_video_encoder.cc b/media/cast/video_sender/external_video_encoder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..8b046f59981f516aafe276fcbcf0a79afe49339e
--- /dev/null
+++ b/media/cast/video_sender/external_video_encoder.cc
@@ -0,0 +1,432 @@
+// Copyright 2013 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/video_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 "media/base/video_frame.h"
+#include "media/base/video_util.h"
+#include "media/cast/cast_defines.h"
+#include "media/video/video_encode_accelerator.h"
+
+namespace {
+ // The number of input buffers allocated, more than what is requested by
+ // RequireBitstreamBuffers().
mikhal1 2013/12/17 22:39:47 The comment is unclear to me.
pwestin 2013/12/19 16:03:47 Done.
+ static const int kInputBufferExtraCount = 1;
+ static const int kOutputBufferCount = 3;
+
+ void LogFrameEncodedEvent(
+ media::cast::CastEnvironment* const cast_environment,
+ const base::TimeTicks& capture_time) {
+ cast_environment->Logging()->InsertFrameEvent(
+ media::cast::kVideoFrameEncoded,
+ media::cast::GetVideoRtpTimestamp(capture_time),
+ media::cast::kFrameIdUnknown);
+ }
+} // 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:
+ LocalVideoEncodeAcceleratorClient(
+ scoped_refptr<CastEnvironment> cast_environment,
+ scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories,
+ const base::WeakPtr<ExternalVideoEncoder>& weak_owner)
+ : cast_environment_(cast_environment),
+ gpu_factories_(gpu_factories),
+ encoder_message_loop_(gpu_factories->GetMessageLoop()),
+ weak_owner_(weak_owner),
+ last_encoded_frame_id_(kStartFrameId) {
+ DCHECK(encoder_message_loop_);
+ }
+
+ // Initialize the real HW encoder.
+ void Initialize(const VideoSenderConfig& video_config) {
+ DCHECK(gpu_factories_);
+ DCHECK(encoder_message_loop_);
+
+ DCHECK(encoder_message_loop_->RunsTasksOnCurrentThread());
+
+ video_encode_accelerator_ =
+ gpu_factories_->CreateVideoEncodeAccelerator(this).Pass();
+ if (!video_encode_accelerator_) return;
mikhal1 2013/12/17 22:39:47 How would the caller know that this wasn't a valid
pwestin 2013/12/19 16:03:47 Will add that in a future CL
mikhal1 2013/12/19 17:23:10 Add a todo On 2013/12/19 16:03:47, pwestin wrote:
+
+ VideoCodecProfile output_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN;
+ switch (video_config.codec) {
+ case kVp8:
+ output_profile = media::VP8PROFILE_MAIN;
+ break;
+ case kH264:
+ output_profile = media::H264PROFILE_MAIN;
+ break;
+ }
+ codec_ = video_config.codec;
+ max_frame_rate_ = video_config.max_frame_rate;
+
+ // Asynchronous initialization call; NotifyInitializeDone or NotifyError
+ // will be called once the HW is initialized.
+ video_encode_accelerator_->Initialize(
+ media::VideoFrame::I420,
+ gfx::Size(video_config.width, video_config.height),
+ output_profile,
+ video_config.start_bitrate);
+ }
+
+ // Free the HW.
+ void Destroy() {
+ DCHECK(encoder_message_loop_);
+ DCHECK(encoder_message_loop_->RunsTasksOnCurrentThread());
+
+ if (video_encode_accelerator_) {
+ video_encode_accelerator_.release()->Destroy();
+ }
+ }
+
+ void SetBitRate(uint32 bit_rate) {
+ DCHECK(encoder_message_loop_);
+ DCHECK(encoder_message_loop_->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_message_loop_);
+ DCHECK(encoder_message_loop_->RunsTasksOnCurrentThread());
+
+ if (input_buffers_free_.empty()) {
+ NOTREACHED();
+ VLOG(2) << "EncodeVideoFrame(): drop frame due to no hw buffers";
+ return;
+ }
+ const int index = input_buffers_free_.back();
+ base::SharedMemory* input_buffer = input_buffers_[index];
+
+ // TODO(pwestin): this allocation and copy can be removed once we don't
+ // pass the video frame through liblingle.
+
+ scoped_refptr<media::VideoFrame> frame =
+ media::VideoFrame::WrapExternalPackedMemory(
+ video_frame->format(),
+ video_frame->coded_size(),
+ video_frame->visible_rect(),
+ video_frame->natural_size(),
+ reinterpret_cast<uint8*>(input_buffer->memory()),
+ input_buffer->mapped_size(),
+ input_buffer->handle(),
+ video_frame->GetTimestamp(),
+ base::Bind(&LocalVideoEncodeAcceleratorClient::FinishedWithInBuffer,
+ this, index));
mikhal1 2013/12/17 22:39:47 ident
pwestin 2013/12/19 16:03:47 Done.
mikhal1 2013/12/19 17:23:10 don't think this is right either. On 2013/12/19 16
+
+ if (!frame) {
+ VLOG(1) << "EncodeVideoFrame(): failed to create frame";
+ NotifyError(media::VideoEncodeAccelerator::kPlatformFailureError);
+ return;
+ }
+ // Do a stride copy of the input frame to match the input requirements.
+ media::CopyYPlane(video_frame->data(VideoFrame::kYPlane),
+ video_frame->stride(VideoFrame::kYPlane),
+ video_frame->natural_size().height(),
+ frame.get());
+ media::CopyUPlane(video_frame->data(VideoFrame::kUPlane),
+ video_frame->stride(VideoFrame::kUPlane),
+ video_frame->natural_size().height(),
+ frame.get());
+ media::CopyVPlane(video_frame->data(VideoFrame::kVPlane),
+ video_frame->stride(VideoFrame::kVPlane),
+ video_frame->natural_size().height(),
+ frame.get());
+
+ 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(frame, key_frame_requested);
+ }
+
+ protected:
+ virtual void NotifyInitializeDone() OVERRIDE {
+ DCHECK(encoder_message_loop_);
+ DCHECK(encoder_message_loop_->RunsTasksOnCurrentThread());
+
+ cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
+ base::Bind(&ExternalVideoEncoder::EncoderInitialized,
+ weak_owner_));
+ }
+
+ virtual void NotifyError(VideoEncodeAccelerator::Error error) OVERRIDE {
+ DCHECK(encoder_message_loop_);
+ DCHECK(encoder_message_loop_->RunsTasksOnCurrentThread());
+ VLOG(1) << "ExternalVideoEncoder NotifyError: " << error;
+
+ if (video_encode_accelerator_) {
+ video_encode_accelerator_.release()->Destroy();
+ }
+ cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
+ base::Bind(&ExternalVideoEncoder::EncoderError,
+ weak_owner_));
+ }
+
+ // Called to allocate the in and output buffers.
mikhal1 2013/12/17 22:39:47 input for consistency
pwestin 2013/12/19 16:03:47 Done.
+ virtual void RequireBitstreamBuffers(unsigned int input_count,
+ const gfx::Size& input_coded_size,
+ size_t output_buffer_size) OVERRIDE {
+ DCHECK(encoder_message_loop_);
+ DCHECK(encoder_message_loop_->RunsTasksOnCurrentThread());
+ DCHECK(video_encode_accelerator_);
+
+ for (unsigned int i = 0; i < input_count + kInputBufferExtraCount; ++i) {
+ base::SharedMemory* shm = gpu_factories_->CreateSharedMemory(
+ media::VideoFrame::AllocationSize(media::VideoFrame::I420,
+ input_coded_size));
+ if (!shm) {
+ VLOG(1) << "RequireBitstreamBuffers(): failed to create input buffer ";
+ return;
+ }
+ input_buffers_.push_back(shm);
+ input_buffers_free_.push_back(i);
+ }
+
+ for (int j = 0; j < kOutputBufferCount; ++j) {
+ base::SharedMemory* shm =
+ gpu_factories_->CreateSharedMemory(output_buffer_size);
+ if (!shm) {
+ VLOG(1) << "RequireBitstreamBuffers(): failed to create input buffer ";
+ return;
+ }
+ output_buffers_.push_back(shm);
+ }
+ // Immediately provide all output buffers to the VEA.
+ for (size_t i = 0; i < output_buffers_.size(); ++i) {
+ video_encode_accelerator_->UseOutputBitstreamBuffer(
+ media::BitstreamBuffer(i, output_buffers_[i]->handle(),
+ output_buffers_[i]->mapped_size()));
+ }
+ }
+
+ // 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_message_loop_);
+ DCHECK(encoder_message_loop_->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 (encoded_frame_data_storage_.empty()) {
+ NOTREACHED();
+ NotifyError(media::VideoEncodeAccelerator::kPlatformFailureError);
+ return;
+ }
+ scoped_ptr<EncodedVideoFrame> encoded_frame(new EncodedVideoFrame());
+
+ encoded_frame->codec = codec_;
+ encoded_frame->key_frame = key_frame;
+ encoded_frame->last_referenced_frame_id = last_encoded_frame_id_;
+ last_encoded_frame_id_++;
+ encoded_frame->frame_id = last_encoded_frame_id_;
+ if (key_frame) {
+ // Self referenced.
+ encoded_frame->last_referenced_frame_id = encoded_frame->frame_id;
+ }
+
+ encoded_frame->data.insert(
+ 0, static_cast<const char*>(output_buffer->memory()), payload_size);
+
+ 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_.front().capture_time));
+
+ cast_environment_->PostTask(CastEnvironment::MAIN, FROM_HERE,
+ base::Bind(LogFrameEncodedEvent, cast_environment_,
+ encoded_frame_data_storage_.front().capture_time));
+
+ encoded_frame_data_storage_.pop_front();
+
+ // 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:
+ // Encoder is done with the provided input buffer.
+ void FinishedWithInBuffer(int index) {
mikhal1 2013/12/17 22:39:47 input
pwestin 2013/12/19 16:03:47 Done.
+ DCHECK(encoder_message_loop_);
+ DCHECK(encoder_message_loop_->RunsTasksOnCurrentThread());
+ DCHECK_GE(index, 0);
+ DCHECK_LT(index, static_cast<int>(input_buffers_.size()));
+ VLOG(2) << "EncodeFrameFinished(): index=" << index;
+ input_buffers_free_.push_back(index);
+ }
+
+ friend class base::RefCountedThreadSafe<LocalVideoEncodeAcceleratorClient>;
+
+ virtual ~LocalVideoEncodeAcceleratorClient() {}
+
+ const scoped_refptr<CastEnvironment> cast_environment_;
+ scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories_;
+ scoped_refptr<base::MessageLoopProxy> encoder_message_loop_;
+ const base::WeakPtr<ExternalVideoEncoder> weak_owner_;
+
+ scoped_ptr<media::VideoEncodeAccelerator> video_encode_accelerator_;
+ int max_frame_rate_;
+ VideoCodec codec_;
+ uint32 last_encoded_frame_id_;
+
+ // Shared memory buffers for input/output with the VideoAccelerator.
+ ScopedVector<base::SharedMemory> input_buffers_;
+ ScopedVector<base::SharedMemory> output_buffers_;
+
+ // Input buffers ready to be filled with input from Encode(). As a LIFO since
+ // we don't care about ordering.
+ std::vector<int> input_buffers_free_;
+
+ // 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,
+ scoped_refptr<GpuVideoAcceleratorFactories> gpu_factories)
+ : video_config_(video_config),
+ cast_environment_(cast_environment),
+ encoder_active_(false),
+ key_frame_requested_(false),
+ skip_next_frame_(false),
+ skip_count_(0),
+ encoder_message_loop_(gpu_factories->GetMessageLoop()),
+ weak_factory_(this) {
+ DCHECK(gpu_factories);
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ video_accelerator_client_ = new LocalVideoEncodeAcceleratorClient(
+ cast_environment, gpu_factories, weak_factory_.GetWeakPtr());
+
+ encoder_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalVideoEncodeAcceleratorClient::Initialize,
+ video_accelerator_client_, video_config));
+}
+
+ExternalVideoEncoder::~ExternalVideoEncoder() {
+ encoder_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalVideoEncodeAcceleratorClient::Destroy,
+ video_accelerator_client_));
+}
+
+void ExternalVideoEncoder::EncoderInitialized() {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ encoder_active_ = true;
+}
+
+void ExternalVideoEncoder::EncoderError() {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ encoder_active_ = false;
+}
+
+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;
+
+ if (skip_next_frame_) {
mikhal1 2013/12/17 22:39:47 set this to false after skipping.
pwestin 2013/12/19 16:03:47 Done.
+ ++skip_count_;
+ VLOG(1) << "Skip encoding frame";
+ return false;
+ }
+ cast_environment_->Logging()->InsertFrameEvent(kVideoFrameSentToEncoder,
+ GetVideoRtpTimestamp(capture_time), kFrameIdUnknown);
+
+ encoder_message_loop_->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) {
+ encoder_message_loop_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalVideoEncodeAcceleratorClient::SetBitRate,
+ video_accelerator_client_, new_bit_rate));
+}
+
+// Inform the encoder to not encode the next frame.
+void ExternalVideoEncoder::SkipNextFrame(bool skip_next_frame) {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ skip_next_frame_ = skip_next_frame;
+}
+
+// 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.
mikhal1 2013/12/17 22:39:47 Add NotSuPPORTED
pwestin 2013/12/19 16:03:47 It will still be called
+}
+
+int ExternalVideoEncoder::NumberOfSkippedFrames() const {
+ DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN));
+ return skip_count_;
+}
+
+} // namespace cast
+} // namespace media

Powered by Google App Engine
This is Rietveld 408576698