summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/test/ppapi/ppapi_browsertest.cc2
-rw-r--r--content/common/gpu/gpu_process_launch_causes.h1
-rw-r--r--content/content_renderer.gypi2
-rw-r--r--content/renderer/pepper/content_renderer_pepper_host_factory.cc4
-rw-r--r--content/renderer/pepper/pepper_video_encoder_host.cc652
-rw-r--r--content/renderer/pepper/pepper_video_encoder_host.h158
-rw-r--r--content/test/ppapi/ppapi_browsertest.cc2
-rw-r--r--ppapi/ppapi_sources.gypi2
-rw-r--r--ppapi/ppapi_tests.gypi1
-rw-r--r--ppapi/proxy/ppapi_messages.h49
-rw-r--r--ppapi/proxy/video_encoder_resource.cc378
-rw-r--r--ppapi/proxy/video_encoder_resource.h101
-rw-r--r--ppapi/proxy/video_encoder_resource_unittest.cc1085
-rw-r--r--ppapi/tests/test_video_encoder.cc41
-rw-r--r--ppapi/tests/test_video_encoder.h29
15 files changed, 2491 insertions, 16 deletions
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc
index 8aa2522..37f6a8c 100644
--- a/chrome/test/ppapi/ppapi_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_browsertest.cc
@@ -1239,6 +1239,8 @@ TEST_PPAPI_NACL(TrueTypeFont)
TEST_PPAPI_NACL(VideoDecoder)
+TEST_PPAPI_NACL(VideoEncoder)
+
// VideoDestination doesn't work in content_browsertests.
TEST_PPAPI_OUT_OF_PROCESS(VideoDestination)
TEST_PPAPI_NACL(VideoDestination)
diff --git a/content/common/gpu/gpu_process_launch_causes.h b/content/common/gpu/gpu_process_launch_causes.h
index c6416ae..b4dfc0a 100644
--- a/content/common/gpu/gpu_process_launch_causes.h
+++ b/content/common/gpu/gpu_process_launch_causes.h
@@ -19,6 +19,7 @@ enum CauseForGpuLaunch {
CAUSE_FOR_GPU_LAUNCH_PEPPERPLATFORMCONTEXT3DIMPL_INITIALIZE,
CAUSE_FOR_GPU_LAUNCH_BROWSER_STARTUP,
CAUSE_FOR_GPU_LAUNCH_CANVAS_2D,
+ CAUSE_FOR_GPU_LAUNCH_PEPPERVIDEOENCODERACCELERATOR_INITIALIZE,
// All new values should be inserted above this point so that
// existing values continue to match up with those in histograms.xml.
diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi
index d3e4979..9b1875b 100644
--- a/content/content_renderer.gypi
+++ b/content/content_renderer.gypi
@@ -520,6 +520,8 @@
'renderer/pepper/pepper_video_capture_host.h',
'renderer/pepper/pepper_video_decoder_host.cc',
'renderer/pepper/pepper_video_decoder_host.h',
+ 'renderer/pepper/pepper_video_encoder_host.cc',
+ 'renderer/pepper/pepper_video_encoder_host.h',
'renderer/pepper/pepper_webplugin_impl.cc',
'renderer/pepper/pepper_webplugin_impl.h',
'renderer/pepper/pepper_websocket_host.cc',
diff --git a/content/renderer/pepper/content_renderer_pepper_host_factory.cc b/content/renderer/pepper/content_renderer_pepper_host_factory.cc
index 143fb18..2df65f7 100644
--- a/content/renderer/pepper/content_renderer_pepper_host_factory.cc
+++ b/content/renderer/pepper/content_renderer_pepper_host_factory.cc
@@ -22,6 +22,7 @@
#include "content/renderer/pepper/pepper_video_capture_host.h"
#include "content/renderer/pepper/pepper_video_decoder_host.h"
#include "content/renderer/pepper/pepper_video_destination_host.h"
+#include "content/renderer/pepper/pepper_video_encoder_host.h"
#include "content/renderer/pepper/pepper_video_source_host.h"
#include "content/renderer/pepper/pepper_websocket_host.h"
#include "content/renderer/pepper/ppb_image_data_impl.h"
@@ -159,6 +160,9 @@ scoped_ptr<ResourceHost> ContentRendererPepperHostFactory::CreateResourceHost(
case PpapiHostMsg_VideoDecoder_Create::ID:
return scoped_ptr<ResourceHost>(
new PepperVideoDecoderHost(host_, instance, resource));
+ case PpapiHostMsg_VideoEncoder_Create::ID:
+ return scoped_ptr<ResourceHost>(
+ new PepperVideoEncoderHost(host_, instance, resource));
case PpapiHostMsg_WebSocket_Create::ID:
return scoped_ptr<ResourceHost>(
new PepperWebSocketHost(host_, instance, resource));
diff --git a/content/renderer/pepper/pepper_video_encoder_host.cc b/content/renderer/pepper/pepper_video_encoder_host.cc
new file mode 100644
index 0000000..bacbfe0
--- /dev/null
+++ b/content/renderer/pepper/pepper_video_encoder_host.cc
@@ -0,0 +1,652 @@
+// Copyright 2015 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/bind.h"
+#include "base/memory/shared_memory.h"
+#include "base/numerics/safe_math.h"
+#include "content/common/gpu/client/command_buffer_proxy_impl.h"
+#include "content/common/gpu/client/gpu_video_encode_accelerator_host.h"
+#include "content/public/renderer/renderer_ppapi_host.h"
+#include "content/renderer/pepper/gfx_conversion.h"
+#include "content/renderer/pepper/host_globals.h"
+#include "content/renderer/pepper/pepper_video_encoder_host.h"
+#include "content/renderer/render_thread_impl.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/video_frame.h"
+#include "media/filters/gpu_video_accelerator_factories.h"
+#include "media/video/video_encode_accelerator.h"
+#include "ppapi/c/pp_codecs.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/pp_graphics_3d.h"
+#include "ppapi/host/dispatch_host_message.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/media_stream_buffer.h"
+
+using ppapi::proxy::SerializedHandle;
+
+namespace content {
+
+namespace {
+
+const uint32_t kDefaultNumberOfBitstreamBuffers = 4;
+
+base::PlatformFile ConvertSharedMemoryHandle(
+ const base::SharedMemory& shared_memory) {
+#if defined(OS_POSIX)
+ return shared_memory.handle().fd;
+#elif defined(OS_WIN)
+ return shared_memory.handle();
+#else
+#error "Platform not supported."
+#endif
+}
+
+int32_t PP_FromMediaEncodeAcceleratorError(
+ media::VideoEncodeAccelerator::Error error) {
+ switch (error) {
+ case media::VideoEncodeAccelerator::kInvalidArgumentError:
+ return PP_ERROR_MALFORMED_INPUT;
+ case media::VideoEncodeAccelerator::kIllegalStateError:
+ case media::VideoEncodeAccelerator::kPlatformFailureError:
+ return PP_ERROR_RESOURCE_FAILED;
+ // No default case, to catch unhandled enum values.
+ }
+ return PP_ERROR_FAILED;
+}
+
+// TODO(llandwerlin): move following to media_conversion.cc/h?
+media::VideoCodecProfile PP_ToMediaVideoProfile(PP_VideoProfile profile) {
+ switch (profile) {
+ case PP_VIDEOPROFILE_H264BASELINE:
+ return media::H264PROFILE_BASELINE;
+ case PP_VIDEOPROFILE_H264MAIN:
+ return media::H264PROFILE_MAIN;
+ case PP_VIDEOPROFILE_H264EXTENDED:
+ return media::H264PROFILE_EXTENDED;
+ case PP_VIDEOPROFILE_H264HIGH:
+ return media::H264PROFILE_HIGH;
+ case PP_VIDEOPROFILE_H264HIGH10PROFILE:
+ return media::H264PROFILE_HIGH10PROFILE;
+ case PP_VIDEOPROFILE_H264HIGH422PROFILE:
+ return media::H264PROFILE_HIGH422PROFILE;
+ case PP_VIDEOPROFILE_H264HIGH444PREDICTIVEPROFILE:
+ return media::H264PROFILE_HIGH444PREDICTIVEPROFILE;
+ case PP_VIDEOPROFILE_H264SCALABLEBASELINE:
+ return media::H264PROFILE_SCALABLEBASELINE;
+ case PP_VIDEOPROFILE_H264SCALABLEHIGH:
+ return media::H264PROFILE_SCALABLEHIGH;
+ case PP_VIDEOPROFILE_H264STEREOHIGH:
+ return media::H264PROFILE_STEREOHIGH;
+ case PP_VIDEOPROFILE_H264MULTIVIEWHIGH:
+ return media::H264PROFILE_MULTIVIEWHIGH;
+ case PP_VIDEOPROFILE_VP8_ANY:
+ return media::VP8PROFILE_ANY;
+ case PP_VIDEOPROFILE_VP9_ANY:
+ return media::VP9PROFILE_ANY;
+ // No default case, to catch unhandled PP_VideoProfile values.
+ }
+ return media::VIDEO_CODEC_PROFILE_UNKNOWN;
+}
+
+PP_VideoProfile PP_FromMediaVideoProfile(media::VideoCodecProfile profile) {
+ switch (profile) {
+ case media::H264PROFILE_BASELINE:
+ return PP_VIDEOPROFILE_H264BASELINE;
+ case media::H264PROFILE_MAIN:
+ return PP_VIDEOPROFILE_H264MAIN;
+ case media::H264PROFILE_EXTENDED:
+ return PP_VIDEOPROFILE_H264EXTENDED;
+ case media::H264PROFILE_HIGH:
+ return PP_VIDEOPROFILE_H264HIGH;
+ case media::H264PROFILE_HIGH10PROFILE:
+ return PP_VIDEOPROFILE_H264HIGH10PROFILE;
+ case media::H264PROFILE_HIGH422PROFILE:
+ return PP_VIDEOPROFILE_H264HIGH422PROFILE;
+ case media::H264PROFILE_HIGH444PREDICTIVEPROFILE:
+ return PP_VIDEOPROFILE_H264HIGH444PREDICTIVEPROFILE;
+ case media::H264PROFILE_SCALABLEBASELINE:
+ return PP_VIDEOPROFILE_H264SCALABLEBASELINE;
+ case media::H264PROFILE_SCALABLEHIGH:
+ return PP_VIDEOPROFILE_H264SCALABLEHIGH;
+ case media::H264PROFILE_STEREOHIGH:
+ return PP_VIDEOPROFILE_H264STEREOHIGH;
+ case media::H264PROFILE_MULTIVIEWHIGH:
+ return PP_VIDEOPROFILE_H264MULTIVIEWHIGH;
+ case media::VP8PROFILE_ANY:
+ return PP_VIDEOPROFILE_VP8_ANY;
+ case media::VP9PROFILE_ANY:
+ return PP_VIDEOPROFILE_VP9_ANY;
+ default:
+ NOTREACHED();
+ return static_cast<PP_VideoProfile>(-1);
+ }
+}
+
+media::VideoFrame::Format PP_ToMediaVideoFormat(PP_VideoFrame_Format format) {
+ switch (format) {
+ case PP_VIDEOFRAME_FORMAT_UNKNOWN:
+ return media::VideoFrame::UNKNOWN;
+ case PP_VIDEOFRAME_FORMAT_YV12:
+ return media::VideoFrame::YV12;
+ case PP_VIDEOFRAME_FORMAT_I420:
+ return media::VideoFrame::I420;
+ case PP_VIDEOFRAME_FORMAT_BGRA:
+ return media::VideoFrame::UNKNOWN;
+ // No default case, to catch unhandled PP_VideoFrame_Format values.
+ }
+ return media::VideoFrame::UNKNOWN;
+}
+
+PP_VideoFrame_Format PP_FromMediaVideoFormat(media::VideoFrame::Format format) {
+ switch (format) {
+ case media::VideoFrame::UNKNOWN:
+ return PP_VIDEOFRAME_FORMAT_UNKNOWN;
+ case media::VideoFrame::YV12:
+ return PP_VIDEOFRAME_FORMAT_YV12;
+ case media::VideoFrame::I420:
+ return PP_VIDEOFRAME_FORMAT_I420;
+ default:
+ return PP_VIDEOFRAME_FORMAT_UNKNOWN;
+ }
+}
+
+PP_VideoProfileDescription PP_FromVideoEncodeAcceleratorSupportedProfile(
+ media::VideoEncodeAccelerator::SupportedProfile profile,
+ PP_HardwareAcceleration acceleration) {
+ PP_VideoProfileDescription pp_profile;
+ pp_profile.profile = PP_FromMediaVideoProfile(profile.profile);
+ pp_profile.max_resolution = PP_FromGfxSize(profile.max_resolution);
+ pp_profile.max_framerate_numerator = profile.max_framerate_numerator;
+ pp_profile.max_framerate_denominator = profile.max_framerate_denominator;
+ pp_profile.acceleration = acceleration;
+ return pp_profile;
+}
+
+bool PP_HardwareAccelerationCompatible(PP_HardwareAcceleration supply,
+ PP_HardwareAcceleration demand) {
+ switch (supply) {
+ case PP_HARDWAREACCELERATION_ONLY:
+ return (demand == PP_HARDWAREACCELERATION_ONLY ||
+ demand == PP_HARDWAREACCELERATION_WITHFALLBACK);
+ case PP_HARDWAREACCELERATION_WITHFALLBACK:
+ return true;
+ case PP_HARDWAREACCELERATION_NONE:
+ return (demand == PP_HARDWAREACCELERATION_WITHFALLBACK ||
+ demand == PP_HARDWAREACCELERATION_NONE);
+ // No default case, to catch unhandled PP_HardwareAcceleration values.
+ }
+ return false;
+}
+
+} // namespace
+
+PepperVideoEncoderHost::ShmBuffer::ShmBuffer(uint32_t id,
+ scoped_ptr<base::SharedMemory> shm)
+ : id(id), shm(shm.Pass()), in_use(true) {
+ DCHECK(this->shm);
+}
+
+PepperVideoEncoderHost::ShmBuffer::~ShmBuffer() {
+}
+
+media::BitstreamBuffer PepperVideoEncoderHost::ShmBuffer::ToBitstreamBuffer() {
+ return media::BitstreamBuffer(id, shm->handle(), shm->mapped_size());
+}
+
+PepperVideoEncoderHost::PepperVideoEncoderHost(RendererPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource)
+ : ResourceHost(host->GetPpapiHost(), instance, resource),
+ renderer_ppapi_host_(host),
+ buffer_manager_(this),
+ command_buffer_(nullptr),
+ initialized_(false),
+ encoder_last_error_(PP_ERROR_FAILED),
+ frame_count_(0),
+ media_input_format_(media::VideoFrame::UNKNOWN),
+ weak_ptr_factory_(this) {
+}
+
+PepperVideoEncoderHost::~PepperVideoEncoderHost() {
+ Close();
+}
+
+int32_t PepperVideoEncoderHost::OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) {
+ PPAPI_BEGIN_MESSAGE_MAP(PepperVideoEncoderHost, msg)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_VideoEncoder_GetSupportedProfiles,
+ OnHostMsgGetSupportedProfiles)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoEncoder_Initialize,
+ OnHostMsgInitialize)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
+ PpapiHostMsg_VideoEncoder_GetVideoFrames,
+ OnHostMsgGetVideoFrames)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VideoEncoder_Encode,
+ OnHostMsgEncode)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer,
+ OnHostMsgRecycleBitstreamBuffer)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL(
+ PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange,
+ OnHostMsgRequestEncodingParametersChange)
+ PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_VideoEncoder_Close,
+ OnHostMsgClose)
+ PPAPI_END_MESSAGE_MAP()
+ return PP_ERROR_FAILED;
+}
+
+int32_t PepperVideoEncoderHost::OnHostMsgGetSupportedProfiles(
+ ppapi::host::HostMessageContext* context) {
+ std::vector<PP_VideoProfileDescription> pp_profiles;
+ GetSupportedProfiles(&pp_profiles);
+
+ host()->SendReply(
+ context->MakeReplyMessageContext(),
+ PpapiPluginMsg_VideoEncoder_GetSupportedProfilesReply(pp_profiles));
+
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperVideoEncoderHost::OnHostMsgInitialize(
+ ppapi::host::HostMessageContext* context,
+ PP_VideoFrame_Format input_format,
+ const PP_Size& input_visible_size,
+ PP_VideoProfile output_profile,
+ uint32_t initial_bitrate,
+ PP_HardwareAcceleration acceleration) {
+ if (initialized_)
+ return PP_ERROR_FAILED;
+
+ media_input_format_ = PP_ToMediaVideoFormat(input_format);
+ if (media_input_format_ == media::VideoFrame::UNKNOWN)
+ return PP_ERROR_BADARGUMENT;
+
+ media::VideoCodecProfile media_profile =
+ PP_ToMediaVideoProfile(output_profile);
+ if (media_profile == media::VIDEO_CODEC_PROFILE_UNKNOWN)
+ return PP_ERROR_BADARGUMENT;
+
+ gfx::Size input_size(input_visible_size.width, input_visible_size.height);
+ if (input_size.IsEmpty())
+ return PP_ERROR_BADARGUMENT;
+
+ if (!IsInitializationValid(input_visible_size, output_profile, acceleration))
+ return PP_ERROR_NOTSUPPORTED;
+
+ int32_t error = PP_ERROR_NOTSUPPORTED;
+ initialize_reply_context_ = context->MakeReplyMessageContext();
+
+ if (acceleration == PP_HARDWAREACCELERATION_ONLY ||
+ acceleration == PP_HARDWAREACCELERATION_WITHFALLBACK) {
+ if (InitializeHardware(media_input_format_, input_size, media_profile,
+ initial_bitrate))
+ return PP_OK_COMPLETIONPENDING;
+
+ if (acceleration == PP_HARDWAREACCELERATION_ONLY)
+ error = PP_ERROR_FAILED;
+ }
+
+ // TODO(llandwerlin): Software encoder.
+ initialize_reply_context_ = ppapi::host::ReplyMessageContext();
+ Close();
+ return error;
+}
+
+int32_t PepperVideoEncoderHost::OnHostMsgGetVideoFrames(
+ ppapi::host::HostMessageContext* context) {
+ if (encoder_last_error_)
+ return encoder_last_error_;
+
+ get_video_frames_reply_context_ = context->MakeReplyMessageContext();
+ AllocateVideoFrames();
+
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperVideoEncoderHost::OnHostMsgEncode(
+ ppapi::host::HostMessageContext* context,
+ uint32_t frame_id,
+ bool force_keyframe) {
+ if (encoder_last_error_)
+ return encoder_last_error_;
+
+ if (frame_id >= frame_count_)
+ return PP_ERROR_FAILED;
+
+ encoder_->Encode(
+ CreateVideoFrame(frame_id, context->MakeReplyMessageContext()),
+ force_keyframe);
+
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t PepperVideoEncoderHost::OnHostMsgRecycleBitstreamBuffer(
+ ppapi::host::HostMessageContext* context,
+ uint32_t buffer_id) {
+ if (encoder_last_error_)
+ return encoder_last_error_;
+
+ if (buffer_id >= shm_buffers_.size() || shm_buffers_[buffer_id]->in_use)
+ return PP_ERROR_FAILED;
+
+ shm_buffers_[buffer_id]->in_use = true;
+ encoder_->UseOutputBitstreamBuffer(
+ shm_buffers_[buffer_id]->ToBitstreamBuffer());
+
+ return PP_OK;
+}
+
+int32_t PepperVideoEncoderHost::OnHostMsgRequestEncodingParametersChange(
+ ppapi::host::HostMessageContext* context,
+ uint32_t bitrate,
+ uint32_t framerate) {
+ if (encoder_last_error_)
+ return encoder_last_error_;
+
+ encoder_->RequestEncodingParametersChange(bitrate, framerate);
+
+ return PP_OK;
+}
+
+int32_t PepperVideoEncoderHost::OnHostMsgClose(
+ ppapi::host::HostMessageContext* context) {
+ encoder_last_error_ = PP_ERROR_FAILED;
+ Close();
+
+ return PP_OK;
+}
+
+void PepperVideoEncoderHost::RequireBitstreamBuffers(
+ unsigned int frame_count,
+ const gfx::Size& input_coded_size,
+ size_t output_buffer_size) {
+ DCHECK(RenderThreadImpl::current());
+ // We assume RequireBitstreamBuffers is only called once.
+ DCHECK(!initialized_);
+
+ input_coded_size_ = input_coded_size;
+ frame_count_ = frame_count;
+
+ for (uint32_t i = 0; i < kDefaultNumberOfBitstreamBuffers; ++i) {
+ scoped_ptr<base::SharedMemory> shm(
+ RenderThread::Get()
+ ->HostAllocateSharedMemoryBuffer(output_buffer_size)
+ .Pass());
+
+ if (!shm || !shm->Map(output_buffer_size)) {
+ shm_buffers_.clear();
+ break;
+ }
+
+ shm_buffers_.push_back(new ShmBuffer(i, shm.Pass()));
+ }
+
+ // Feed buffers to the encoder.
+ std::vector<SerializedHandle> handles;
+ for (size_t i = 0; i < shm_buffers_.size(); ++i) {
+ encoder_->UseOutputBitstreamBuffer(shm_buffers_[i]->ToBitstreamBuffer());
+ handles.push_back(SerializedHandle(
+ renderer_ppapi_host_->ShareHandleWithRemote(
+ ConvertSharedMemoryHandle(*shm_buffers_[i]->shm), false),
+ output_buffer_size));
+ }
+
+ host()->SendUnsolicitedReplyWithHandles(
+ pp_resource(), PpapiPluginMsg_VideoEncoder_BitstreamBuffers(
+ static_cast<uint32_t>(output_buffer_size)),
+ handles);
+
+ if (!initialized_) {
+ // Tell the plugin that initialization has been successful if we
+ // haven't already.
+ initialized_ = true;
+ encoder_last_error_ = PP_OK;
+ host()->SendReply(initialize_reply_context_,
+ PpapiPluginMsg_VideoEncoder_InitializeReply(
+ frame_count, PP_FromGfxSize(input_coded_size)));
+ }
+
+ if (shm_buffers_.empty()) {
+ NotifyPepperError(PP_ERROR_NOMEMORY);
+ return;
+ }
+
+ // If the plugin already requested video frames, we can now answer
+ // that request.
+ if (get_video_frames_reply_context_.is_valid())
+ AllocateVideoFrames();
+}
+
+void PepperVideoEncoderHost::BitstreamBufferReady(int32 buffer_id,
+ size_t payload_size,
+ bool key_frame) {
+ DCHECK(RenderThreadImpl::current());
+ DCHECK(shm_buffers_[buffer_id]->in_use);
+
+ shm_buffers_[buffer_id]->in_use = false;
+ host()->SendUnsolicitedReply(
+ pp_resource(),
+ PpapiPluginMsg_VideoEncoder_BitstreamBufferReady(
+ buffer_id, static_cast<uint32_t>(payload_size), key_frame));
+}
+
+void PepperVideoEncoderHost::NotifyError(
+ media::VideoEncodeAccelerator::Error error) {
+ DCHECK(RenderThreadImpl::current());
+ NotifyPepperError(PP_FromMediaEncodeAcceleratorError(error));
+}
+
+void PepperVideoEncoderHost::GetSupportedProfiles(
+ std::vector<PP_VideoProfileDescription>* pp_profiles) {
+ DCHECK(RenderThreadImpl::current());
+
+ if (!EnsureGpuChannel())
+ return;
+
+ std::vector<media::VideoEncodeAccelerator::SupportedProfile> profiles =
+ GpuVideoEncodeAcceleratorHost::ConvertGpuToMediaProfiles(
+ channel_->gpu_info().video_encode_accelerator_supported_profiles);
+ for (media::VideoEncodeAccelerator::SupportedProfile profile : profiles)
+ pp_profiles->push_back(PP_FromVideoEncodeAcceleratorSupportedProfile(
+ profile, PP_HARDWAREACCELERATION_ONLY));
+
+ // TODO(llandwerlin): add software supported profiles.
+}
+
+bool PepperVideoEncoderHost::IsInitializationValid(
+ const PP_Size& input_size,
+ PP_VideoProfile output_profile,
+ PP_HardwareAcceleration acceleration) {
+ DCHECK(RenderThreadImpl::current());
+
+ std::vector<PP_VideoProfileDescription> profiles;
+ GetSupportedProfiles(&profiles);
+
+ for (const PP_VideoProfileDescription& profile : profiles) {
+ if (output_profile == profile.profile &&
+ input_size.width <= profile.max_resolution.width &&
+ input_size.height <= profile.max_resolution.height &&
+ PP_HardwareAccelerationCompatible(profile.acceleration, acceleration))
+ return true;
+ }
+
+ return false;
+}
+
+bool PepperVideoEncoderHost::EnsureGpuChannel() {
+ DCHECK(RenderThreadImpl::current());
+
+ if (command_buffer_)
+ return true;
+
+ // There is no guarantee that we have a 3D context to work with. So
+ // we create a dummy command buffer to communicate with the gpu process.
+ channel_ = RenderThreadImpl::current()->EstablishGpuChannelSync(
+ CAUSE_FOR_GPU_LAUNCH_PEPPERVIDEOENCODERACCELERATOR_INITIALIZE);
+ if (!channel_)
+ return false;
+
+ std::vector<int32> attribs(1, PP_GRAPHICS3DATTRIB_NONE);
+ command_buffer_ = channel_->CreateOffscreenCommandBuffer(
+ gfx::Size(), nullptr, attribs, GURL::EmptyGURL(),
+ gfx::PreferIntegratedGpu);
+ if (!command_buffer_) {
+ Close();
+ return false;
+ }
+
+ command_buffer_->SetChannelErrorCallback(media::BindToCurrentLoop(
+ base::Bind(&PepperVideoEncoderHost::NotifyPepperError,
+ weak_ptr_factory_.GetWeakPtr(), PP_ERROR_RESOURCE_FAILED)));
+ if (!command_buffer_->Initialize()) {
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+bool PepperVideoEncoderHost::InitializeHardware(
+ media::VideoFrame::Format input_format,
+ const gfx::Size& input_visible_size,
+ media::VideoCodecProfile output_profile,
+ uint32_t initial_bitrate) {
+ DCHECK(RenderThreadImpl::current());
+
+ if (!EnsureGpuChannel())
+ return false;
+
+ encoder_ = command_buffer_->CreateVideoEncoder();
+ if (!encoder_ ||
+ !encoder_->Initialize(input_format, input_visible_size, output_profile,
+ initial_bitrate, this))
+ return false;
+
+ return true;
+}
+
+void PepperVideoEncoderHost::Close() {
+ DCHECK(RenderThreadImpl::current());
+
+ encoder_ = nullptr;
+ if (command_buffer_) {
+ DCHECK(channel_);
+ channel_->DestroyCommandBuffer(command_buffer_);
+ command_buffer_ = nullptr;
+ }
+}
+
+void PepperVideoEncoderHost::AllocateVideoFrames() {
+ DCHECK(RenderThreadImpl::current());
+ DCHECK(get_video_frames_reply_context_.is_valid());
+
+ // Frames have already been allocated.
+ if (buffer_manager_.number_of_buffers() > 0) {
+ SendGetFramesErrorReply(PP_ERROR_FAILED);
+ NOTREACHED();
+ return;
+ }
+
+ base::CheckedNumeric<uint32_t> size =
+ media::VideoFrame::AllocationSize(media_input_format_, input_coded_size_);
+ uint32_t frame_size = size.ValueOrDie();
+ size += sizeof(ppapi::MediaStreamBuffer::Video);
+ uint32_t buffer_size = size.ValueOrDie();
+ // Make each buffer 4 byte aligned.
+ size += (4 - buffer_size % 4);
+ uint32_t buffer_size_aligned = size.ValueOrDie();
+ size *= frame_count_;
+ uint32_t total_size = size.ValueOrDie();
+
+ scoped_ptr<base::SharedMemory> shm(
+ RenderThreadImpl::current()
+ ->HostAllocateSharedMemoryBuffer(total_size)
+ .Pass());
+ if (!shm ||
+ !buffer_manager_.SetBuffers(frame_count_,
+ buffer_size_aligned,
+ shm.Pass(),
+ true)) {
+ SendGetFramesErrorReply(PP_ERROR_NOMEMORY);
+ return;
+ }
+
+ VLOG(4) << " frame_count=" << frame_count_ << " frame_size=" << frame_size
+ << " buffer_size=" << buffer_size_aligned;
+
+ for (int32_t i = 0; i < buffer_manager_.number_of_buffers(); ++i) {
+ ppapi::MediaStreamBuffer::Video* buffer =
+ &(buffer_manager_.GetBufferPointer(i)->video);
+ buffer->header.size = buffer_manager_.buffer_size();
+ buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO;
+ buffer->format = PP_FromMediaVideoFormat(media_input_format_);
+ buffer->size.width = input_coded_size_.width();
+ buffer->size.height = input_coded_size_.height();
+ buffer->data_size = frame_size;
+ }
+
+ DCHECK(get_video_frames_reply_context_.is_valid());
+ get_video_frames_reply_context_.params.AppendHandle(SerializedHandle(
+ renderer_ppapi_host_->ShareHandleWithRemote(
+ ConvertSharedMemoryHandle(*buffer_manager_.shm()), false),
+ total_size));
+
+ host()->SendReply(get_video_frames_reply_context_,
+ PpapiPluginMsg_VideoEncoder_GetVideoFramesReply(
+ frame_count_, buffer_size_aligned,
+ PP_FromGfxSize(input_coded_size_)));
+ get_video_frames_reply_context_ = ppapi::host::ReplyMessageContext();
+}
+
+void PepperVideoEncoderHost::SendGetFramesErrorReply(int32_t error) {
+ get_video_frames_reply_context_.params.set_result(error);
+ host()->SendReply(
+ get_video_frames_reply_context_,
+ PpapiPluginMsg_VideoEncoder_GetVideoFramesReply(0, 0, PP_MakeSize(0, 0)));
+ get_video_frames_reply_context_ = ppapi::host::ReplyMessageContext();
+}
+
+scoped_refptr<media::VideoFrame> PepperVideoEncoderHost::CreateVideoFrame(
+ uint32_t frame_id,
+ const ppapi::host::ReplyMessageContext& reply_context) {
+ DCHECK(RenderThreadImpl::current());
+
+ ppapi::MediaStreamBuffer* buffer = buffer_manager_.GetBufferPointer(frame_id);
+ DCHECK(buffer);
+ uint32_t shm_offset = static_cast<uint8*>(buffer->video.data) -
+ static_cast<uint8*>(buffer_manager_.shm()->memory());
+
+ return media::VideoFrame::WrapExternalPackedMemory(
+ media_input_format_, input_coded_size_, gfx::Rect(input_coded_size_),
+ input_coded_size_, static_cast<uint8*>(buffer->video.data),
+ buffer->video.data_size, buffer_manager_.shm()->handle(), shm_offset,
+ base::TimeDelta(),
+ base::Bind(&PepperVideoEncoderHost::FrameReleased,
+ weak_ptr_factory_.GetWeakPtr(), reply_context, frame_id));
+}
+
+void PepperVideoEncoderHost::FrameReleased(
+ const ppapi::host::ReplyMessageContext& reply_context,
+ uint32_t frame_id) {
+ DCHECK(RenderThreadImpl::current());
+
+ ppapi::host::ReplyMessageContext context = reply_context;
+ context.params.set_result(encoder_last_error_);
+ host()->SendReply(context, PpapiPluginMsg_VideoEncoder_EncodeReply(frame_id));
+}
+
+void PepperVideoEncoderHost::NotifyPepperError(int32_t error) {
+ DCHECK(RenderThreadImpl::current());
+
+ encoder_last_error_ = error;
+ Close();
+ host()->SendUnsolicitedReply(
+ pp_resource(),
+ PpapiPluginMsg_VideoEncoder_NotifyError(encoder_last_error_));
+}
+
+} // namespace content
diff --git a/content/renderer/pepper/pepper_video_encoder_host.h b/content/renderer/pepper/pepper_video_encoder_host.h
new file mode 100644
index 0000000..18c9a8e
--- /dev/null
+++ b/content/renderer/pepper/pepper_video_encoder_host.h
@@ -0,0 +1,158 @@
+// Copyright 2015 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.
+
+#ifndef CONTENT_RENDERER_PEPPER_PEPPER_VIDEO_ENCODER_HOST_H_
+#define CONTENT_RENDERER_PEPPER_PEPPER_VIDEO_ENCODER_HOST_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "content/common/content_export.h"
+#include "media/video/video_encode_accelerator.h"
+#include "ppapi/c/pp_codecs.h"
+#include "ppapi/c/ppb_video_frame.h"
+#include "ppapi/host/host_message_context.h"
+#include "ppapi/host/resource_host.h"
+#include "ppapi/proxy/resource_message_params.h"
+#include "ppapi/shared_impl/media_stream_buffer_manager.h"
+
+namespace media {
+class GpuVideoAcceleratorFactories;
+}
+
+namespace content {
+
+class CommandBufferProxyImpl;
+class GpuChannelHost;
+class RendererPpapiHost;
+
+class CONTENT_EXPORT PepperVideoEncoderHost
+ : public ppapi::host::ResourceHost,
+ public media::VideoEncodeAccelerator::Client,
+ public ppapi::MediaStreamBufferManager::Delegate {
+ public:
+ PepperVideoEncoderHost(RendererPpapiHost* host,
+ PP_Instance instance,
+ PP_Resource resource);
+ ~PepperVideoEncoderHost() override;
+
+ private:
+ // Shared memory buffers.
+ struct ShmBuffer {
+ ShmBuffer(uint32_t id, scoped_ptr<base::SharedMemory> shm);
+ ~ShmBuffer();
+
+ media::BitstreamBuffer ToBitstreamBuffer();
+
+ // Index of the buffer in the ScopedVector. Buffers have the same id in
+ // the plugin and the host.
+ uint32_t id;
+ scoped_ptr<base::SharedMemory> shm;
+ bool in_use;
+ };
+
+ // media::VideoEncodeAccelerator implementation.
+ void RequireBitstreamBuffers(unsigned int input_count,
+ const gfx::Size& input_coded_size,
+ size_t output_buffer_size) override;
+ void BitstreamBufferReady(int32 bitstream_buffer_id,
+ size_t payload_size,
+ bool key_frame) override;
+ void NotifyError(media::VideoEncodeAccelerator::Error error) override;
+
+ // ResourceHost implementation.
+ int32_t OnResourceMessageReceived(
+ const IPC::Message& msg,
+ ppapi::host::HostMessageContext* context) override;
+
+ int32_t OnHostMsgGetSupportedProfiles(
+ ppapi::host::HostMessageContext* context);
+ int32_t OnHostMsgInitialize(ppapi::host::HostMessageContext* context,
+ PP_VideoFrame_Format input_format,
+ const PP_Size& input_visible_size,
+ PP_VideoProfile output_profile,
+ uint32_t initial_bitrate,
+ PP_HardwareAcceleration acceleration);
+ int32_t OnHostMsgGetVideoFrames(ppapi::host::HostMessageContext* context);
+ int32_t OnHostMsgEncode(ppapi::host::HostMessageContext* context,
+ uint32_t frame_id,
+ bool force_keyframe);
+ int32_t OnHostMsgRecycleBitstreamBuffer(
+ ppapi::host::HostMessageContext* context,
+ uint32_t buffer_id);
+ int32_t OnHostMsgRequestEncodingParametersChange(
+ ppapi::host::HostMessageContext* context,
+ uint32_t bitrate,
+ uint32_t framerate);
+ int32_t OnHostMsgClose(ppapi::host::HostMessageContext* context);
+
+ // Internal methods.
+ void GetSupportedProfiles(
+ std::vector<PP_VideoProfileDescription>* pp_profiles);
+ bool IsInitializationValid(const PP_Size& input_size,
+ PP_VideoProfile ouput_profile,
+ PP_HardwareAcceleration acceleration);
+ bool EnsureGpuChannel();
+ bool InitializeHardware(media::VideoFrame::Format input_format,
+ const gfx::Size& input_visible_size,
+ media::VideoCodecProfile output_profile,
+ uint32_t initial_bitrate);
+ void Close();
+ void AllocateVideoFrames();
+ void SendGetFramesErrorReply(int32_t error);
+ scoped_refptr<media::VideoFrame> CreateVideoFrame(
+ uint32_t frame_id,
+ const ppapi::host::ReplyMessageContext& reply_context);
+ void FrameReleased(const ppapi::host::ReplyMessageContext& reply_context,
+ uint32_t frame_id);
+ void NotifyPepperError(int32_t error);
+
+ // Non-owning pointer.
+ RendererPpapiHost* renderer_ppapi_host_;
+
+ ScopedVector<ShmBuffer> shm_buffers_;
+
+ // Buffer manager for shared memory that holds video frames.
+ ppapi::MediaStreamBufferManager buffer_manager_;
+
+ scoped_refptr<GpuChannelHost> channel_;
+ CommandBufferProxyImpl* command_buffer_;
+
+ scoped_ptr<media::VideoEncodeAccelerator> encoder_;
+
+ // Whether the encoder has been successfully initialized.
+ bool initialized_;
+
+ // Saved context to answer an Initialize message from the plugin.
+ ppapi::host::ReplyMessageContext initialize_reply_context_;
+
+ // Saved context to answer a GetVideoFrames message from the plugin.
+ ppapi::host::ReplyMessageContext get_video_frames_reply_context_;
+
+ // This represents the current error state of the encoder, i.e. PP_OK
+ // normally, or a Pepper error code if the encoder is uninitialized,
+ // has been notified of an encoder error, has encountered some
+ // other unrecoverable error, or has been closed by the plugin.
+ // This field is checked in most message handlers to decide whether
+ // operations should proceed or fail.
+ int32_t encoder_last_error_;
+
+ // Size of the frames allocated for the encoder (matching hardware
+ // constraints).
+ gfx::Size input_coded_size_;
+
+ // Number of frames the encoder needs.
+ uint32_t frame_count_;
+
+ // Format of the frames to give to the encoder.
+ media::VideoFrame::Format media_input_format_;
+
+ base::WeakPtrFactory<PepperVideoEncoderHost> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PepperVideoEncoderHost);
+};
+
+} // namespace content
+
+#endif // CONTENT_RENDERER_PEPPER_PEPPER_VIDEO_ENCODER_HOST_H_
diff --git a/content/test/ppapi/ppapi_browsertest.cc b/content/test/ppapi/ppapi_browsertest.cc
index 1a43355..be0764b 100644
--- a/content/test/ppapi/ppapi_browsertest.cc
+++ b/content/test/ppapi/ppapi_browsertest.cc
@@ -162,5 +162,7 @@ TEST_PPAPI_OUT_OF_PROCESS(VideoDecoder)
TEST_PPAPI_IN_PROCESS(VideoDecoderDev)
TEST_PPAPI_OUT_OF_PROCESS(VideoDecoderDev)
+TEST_PPAPI_OUT_OF_PROCESS(VideoEncoder)
+
} // namespace
} // namespace content
diff --git a/ppapi/ppapi_sources.gypi b/ppapi/ppapi_sources.gypi
index e182b05..3536453 100644
--- a/ppapi/ppapi_sources.gypi
+++ b/ppapi/ppapi_sources.gypi
@@ -513,6 +513,8 @@
'tests/test_video_decoder.h',
'tests/test_video_destination.cc',
'tests/test_video_destination.h',
+ 'tests/test_video_encoder.cc',
+ 'tests/test_video_encoder.h',
'tests/test_video_source.cc',
'tests/test_video_source.h',
'tests/test_view.cc',
diff --git a/ppapi/ppapi_tests.gypi b/ppapi/ppapi_tests.gypi
index 0e5de16..562903d 100644
--- a/ppapi/ppapi_tests.gypi
+++ b/ppapi/ppapi_tests.gypi
@@ -175,6 +175,7 @@
'proxy/serialized_var_unittest.cc',
'proxy/talk_resource_unittest.cc',
'proxy/video_decoder_resource_unittest.cc',
+ 'proxy/video_encoder_resource_unittest.cc',
'proxy/websocket_resource_unittest.cc',
'shared_impl/media_stream_audio_track_shared_unittest.cc',
'shared_impl/media_stream_buffer_manager_unittest.cc',
diff --git a/ppapi/proxy/ppapi_messages.h b/ppapi/proxy/ppapi_messages.h
index 243e783..643d8f9 100644
--- a/ppapi/proxy/ppapi_messages.h
+++ b/ppapi/proxy/ppapi_messages.h
@@ -41,6 +41,7 @@
#include "ppapi/c/ppb_tcp_socket.h"
#include "ppapi/c/ppb_text_input_controller.h"
#include "ppapi/c/ppb_udp_socket.h"
+#include "ppapi/c/ppb_video_encoder.h"
#include "ppapi/c/private/pp_content_decryptor.h"
#include "ppapi/c/private/pp_private_font_charset.h"
#include "ppapi/c/private/pp_video_capture_format.h"
@@ -443,6 +444,14 @@ IPC_STRUCT_TRAITS_BEGIN(ppapi::PpapiNaClPluginArgs)
IPC_STRUCT_TRAITS_MEMBER(switch_values)
IPC_STRUCT_TRAITS_END()
+IPC_STRUCT_TRAITS_BEGIN(PP_VideoProfileDescription)
+IPC_STRUCT_TRAITS_MEMBER(profile)
+IPC_STRUCT_TRAITS_MEMBER(max_resolution)
+IPC_STRUCT_TRAITS_MEMBER(max_framerate_numerator)
+IPC_STRUCT_TRAITS_MEMBER(max_framerate_denominator)
+IPC_STRUCT_TRAITS_MEMBER(acceleration)
+IPC_STRUCT_TRAITS_END()
+
#if !defined(OS_NACL) && !defined(NACL_WIN64)
IPC_STRUCT_TRAITS_BEGIN(ppapi::proxy::PPPDecryptor_Buffer)
@@ -1992,6 +2001,46 @@ IPC_MESSAGE_CONTROL0(PpapiPluginMsg_VideoDecoder_ResetReply)
IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoDecoder_NotifyError,
int32_t /* error */)
+// VideoEncoder ------------------------------------------------------
+
+IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoEncoder_Create)
+IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoEncoder_GetSupportedProfiles)
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoEncoder_GetSupportedProfilesReply,
+ std::vector<PP_VideoProfileDescription> /* results */)
+IPC_MESSAGE_CONTROL5(PpapiHostMsg_VideoEncoder_Initialize,
+ PP_VideoFrame_Format /* input_format */,
+ PP_Size /* input_visible_size */,
+ PP_VideoProfile /* output_profile */,
+ uint32_t /* initial_bitrate */,
+ PP_HardwareAcceleration /* acceleration */)
+IPC_MESSAGE_CONTROL2(PpapiPluginMsg_VideoEncoder_InitializeReply,
+ uint32_t /* input_frame_count */,
+ PP_Size /* input_coded_size */)
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoEncoder_BitstreamBuffers,
+ uint32_t /* buffer_length */)
+IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoEncoder_GetVideoFrames)
+IPC_MESSAGE_CONTROL3(PpapiPluginMsg_VideoEncoder_GetVideoFramesReply,
+ uint32_t /* frame_count */,
+ uint32_t /* frame_length */,
+ PP_Size /* frame_size */)
+IPC_MESSAGE_CONTROL2(PpapiHostMsg_VideoEncoder_Encode,
+ uint32_t /* frame_id */,
+ bool /* force_keyframe */)
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoEncoder_EncodeReply,
+ uint32_t /* frame_id */)
+IPC_MESSAGE_CONTROL3(PpapiPluginMsg_VideoEncoder_BitstreamBufferReady,
+ uint32_t /* buffer_id */,
+ uint32_t /* buffer_size */,
+ bool /* key_frame */)
+IPC_MESSAGE_CONTROL1(PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer,
+ uint32_t /* buffer_id */)
+IPC_MESSAGE_CONTROL2(PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange,
+ uint32_t /* bitrate */,
+ uint32_t /* framerate */)
+IPC_MESSAGE_CONTROL1(PpapiPluginMsg_VideoEncoder_NotifyError,
+ int32_t /* error */)
+IPC_MESSAGE_CONTROL0(PpapiHostMsg_VideoEncoder_Close)
+
#if !defined(OS_NACL) && !defined(NACL_WIN64)
// Audio input.
diff --git a/ppapi/proxy/video_encoder_resource.cc b/ppapi/proxy/video_encoder_resource.cc
index 5d7913d..3c6436b 100644
--- a/ppapi/proxy/video_encoder_resource.cc
+++ b/ppapi/proxy/video_encoder_resource.cc
@@ -2,19 +2,73 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/memory/shared_memory.h"
+#include "base/numerics/safe_conversions.h"
+#include "ppapi/c/pp_array_output.h"
+#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/video_encoder_resource.h"
+#include "ppapi/proxy/video_frame_resource.h"
+#include "ppapi/shared_impl/media_stream_buffer.h"
+#include "ppapi/shared_impl/media_stream_buffer_manager.h"
+#include "ppapi/thunk/enter.h"
+using ppapi::proxy::SerializedHandle;
+using ppapi::thunk::EnterResourceNoLock;
using ppapi::thunk::PPB_VideoEncoder_API;
namespace ppapi {
namespace proxy {
+namespace {
+
+void RunCallback(scoped_refptr<TrackedCallback>* callback, int32_t error) {
+ if (!TrackedCallback::IsPending(*callback))
+ return;
+
+ scoped_refptr<TrackedCallback> temp;
+ callback->swap(temp);
+ temp->Run(error);
+}
+
+} // namespace
+
+VideoEncoderResource::ShmBuffer::ShmBuffer(uint32_t id,
+ scoped_ptr<base::SharedMemory> shm)
+ : id(id), shm(shm.Pass()) {
+}
+
+VideoEncoderResource::ShmBuffer::~ShmBuffer() {
+}
+
+VideoEncoderResource::BitstreamBuffer::BitstreamBuffer(uint32_t id,
+ uint32_t size,
+ bool key_frame)
+ : id(id), size(size), key_frame(key_frame) {
+}
+
+VideoEncoderResource::BitstreamBuffer::~BitstreamBuffer() {
+}
+
VideoEncoderResource::VideoEncoderResource(Connection connection,
PP_Instance instance)
- : PluginResource(connection, instance) {
+ : PluginResource(connection, instance),
+ initialized_(false),
+ closed_(false),
+ // Set |encoder_last_error_| to PP_OK after successful initialization.
+ // This makes error checking a little more concise, since we can check
+ // that the encoder has been initialized and hasn't returned an error by
+ // just testing |encoder_last_error_|.
+ encoder_last_error_(PP_ERROR_FAILED),
+ input_frame_count_(0),
+ input_coded_size_(PP_MakeSize(0, 0)),
+ buffer_manager_(this),
+ get_video_frame_data_(nullptr),
+ get_bitstream_buffer_data_(nullptr) {
+ SendCreate(RENDERER, PpapiHostMsg_VideoEncoder_Create());
}
VideoEncoderResource::~VideoEncoderResource() {
+ Close();
}
PPB_VideoEncoder_API* VideoEncoderResource::AsPPB_VideoEncoder_API() {
@@ -24,7 +78,28 @@ PPB_VideoEncoder_API* VideoEncoderResource::AsPPB_VideoEncoder_API() {
int32_t VideoEncoderResource::GetSupportedProfiles(
const PP_ArrayOutput& output,
const scoped_refptr<TrackedCallback>& callback) {
- return PP_ERROR_FAILED;
+ if (TrackedCallback::IsPending(get_supported_profiles_callback_))
+ return PP_ERROR_INPROGRESS;
+
+ get_supported_profiles_callback_ = callback;
+ Call<PpapiPluginMsg_VideoEncoder_GetSupportedProfilesReply>(
+ RENDERER, PpapiHostMsg_VideoEncoder_GetSupportedProfiles(),
+ base::Bind(&VideoEncoderResource::OnPluginMsgGetSupportedProfilesReply,
+ this, output));
+ return PP_OK_COMPLETIONPENDING;
+}
+
+int32_t VideoEncoderResource::GetFramesRequired() {
+ if (encoder_last_error_)
+ return encoder_last_error_;
+ return input_frame_count_;
+}
+
+int32_t VideoEncoderResource::GetFrameCodedSize(PP_Size* size) {
+ if (encoder_last_error_)
+ return encoder_last_error_;
+ *size = input_coded_size_;
+ return PP_OK;
}
int32_t VideoEncoderResource::Initialize(
@@ -34,45 +109,318 @@ int32_t VideoEncoderResource::Initialize(
uint32_t initial_bitrate,
PP_HardwareAcceleration acceleration,
const scoped_refptr<TrackedCallback>& callback) {
- return PP_ERROR_FAILED;
-}
+ if (initialized_)
+ return PP_ERROR_FAILED;
+ if (TrackedCallback::IsPending(initialize_callback_))
+ return PP_ERROR_INPROGRESS;
-int32_t VideoEncoderResource::GetFramesRequired() {
- return PP_ERROR_FAILED;
-}
-
-int32_t VideoEncoderResource::GetFrameCodedSize(PP_Size* size) {
- return PP_ERROR_FAILED;
+ initialize_callback_ = callback;
+ Call<PpapiPluginMsg_VideoEncoder_InitializeReply>(
+ RENDERER, PpapiHostMsg_VideoEncoder_Initialize(
+ input_format, *input_visible_size, output_profile,
+ initial_bitrate, acceleration),
+ base::Bind(&VideoEncoderResource::OnPluginMsgInitializeReply, this));
+ return PP_OK_COMPLETIONPENDING;
}
int32_t VideoEncoderResource::GetVideoFrame(
PP_Resource* video_frame,
const scoped_refptr<TrackedCallback>& callback) {
- return PP_ERROR_FAILED;
+ if (encoder_last_error_)
+ return encoder_last_error_;
+
+ if (TrackedCallback::IsPending(get_video_frame_callback_))
+ return PP_ERROR_INPROGRESS;
+
+ get_video_frame_data_ = video_frame;
+ get_video_frame_callback_ = callback;
+
+ // Lazily ask for a shared memory buffer in which video frames are allocated.
+ if (buffer_manager_.number_of_buffers() == 0) {
+ Call<PpapiPluginMsg_VideoEncoder_GetVideoFramesReply>(
+ RENDERER, PpapiHostMsg_VideoEncoder_GetVideoFrames(),
+ base::Bind(&VideoEncoderResource::OnPluginMsgGetVideoFramesReply,
+ this));
+ } else {
+ TryWriteVideoFrame();
+ }
+
+ return PP_OK_COMPLETIONPENDING;
}
int32_t VideoEncoderResource::Encode(
PP_Resource video_frame,
PP_Bool force_keyframe,
const scoped_refptr<TrackedCallback>& callback) {
- return PP_ERROR_FAILED;
+ if (encoder_last_error_)
+ return encoder_last_error_;
+
+ VideoFrameMap::iterator it = video_frames_.find(video_frame);
+ if (it == video_frames_.end())
+ // TODO(llandwerlin): accept MediaStreamVideoTrack's video frames.
+ return PP_ERROR_BADRESOURCE;
+
+ scoped_refptr<VideoFrameResource> frame_resource = it->second;
+
+ encode_callbacks_.insert(std::make_pair(video_frame, callback));
+
+ Call<PpapiPluginMsg_VideoEncoder_EncodeReply>(
+ RENDERER,
+ PpapiHostMsg_VideoEncoder_Encode(frame_resource->GetBufferIndex(),
+ PP_ToBool(force_keyframe)),
+ base::Bind(&VideoEncoderResource::OnPluginMsgEncodeReply, this,
+ video_frame));
+
+ // Invalidate the frame to prevent the plugin from modifying it.
+ it->second->Invalidate();
+ video_frames_.erase(it);
+
+ return PP_OK_COMPLETIONPENDING;
}
int32_t VideoEncoderResource::GetBitstreamBuffer(
- PP_BitstreamBuffer* picture,
+ PP_BitstreamBuffer* bitstream_buffer,
const scoped_refptr<TrackedCallback>& callback) {
- return PP_ERROR_FAILED;
+ if (encoder_last_error_)
+ return encoder_last_error_;
+ if (TrackedCallback::IsPending(get_bitstream_buffer_callback_))
+ return PP_ERROR_INPROGRESS;
+
+ get_bitstream_buffer_callback_ = callback;
+ get_bitstream_buffer_data_ = bitstream_buffer;
+
+ if (!available_bitstream_buffers_.empty()) {
+ BitstreamBuffer buffer(available_bitstream_buffers_.front());
+ available_bitstream_buffers_.pop_front();
+ WriteBitstreamBuffer(buffer);
+ }
+
+ return PP_OK_COMPLETIONPENDING;
}
void VideoEncoderResource::RecycleBitstreamBuffer(
- const PP_BitstreamBuffer* picture) {
+ const PP_BitstreamBuffer* bitstream_buffer) {
+ if (encoder_last_error_)
+ return;
+ BitstreamBufferMap::const_iterator iter =
+ bitstream_buffer_map_.find(bitstream_buffer->buffer);
+ if (iter != bitstream_buffer_map_.end()) {
+ Post(RENDERER,
+ PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer(iter->second));
+ }
}
void VideoEncoderResource::RequestEncodingParametersChange(uint32_t bitrate,
uint32_t framerate) {
+ if (encoder_last_error_)
+ return;
+ Post(RENDERER, PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange(
+ bitrate, framerate));
}
void VideoEncoderResource::Close() {
+ if (closed_)
+ return;
+ Post(RENDERER, PpapiHostMsg_VideoEncoder_Close());
+ closed_ = true;
+ if (!encoder_last_error_ || !initialized_)
+ NotifyError(PP_ERROR_ABORTED);
+ ReleaseFrames();
+}
+
+void VideoEncoderResource::OnReplyReceived(
+ const ResourceMessageReplyParams& params,
+ const IPC::Message& msg) {
+ PPAPI_BEGIN_MESSAGE_MAP(VideoEncoderResource, msg)
+ PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
+ PpapiPluginMsg_VideoEncoder_BitstreamBuffers,
+ OnPluginMsgBitstreamBuffers)
+ PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
+ PpapiPluginMsg_VideoEncoder_BitstreamBufferReady,
+ OnPluginMsgBitstreamBufferReady)
+ PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(PpapiPluginMsg_VideoEncoder_NotifyError,
+ OnPluginMsgNotifyError)
+ PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(
+ PluginResource::OnReplyReceived(params, msg))
+ PPAPI_END_MESSAGE_MAP()
+}
+
+void VideoEncoderResource::OnPluginMsgGetSupportedProfilesReply(
+ const PP_ArrayOutput& output,
+ const ResourceMessageReplyParams& params,
+ const std::vector<PP_VideoProfileDescription>& profiles) {
+ void* ptr = output.GetDataBuffer(
+ output.user_data,
+ base::checked_cast<uint32_t>(profiles.size()),
+ base::checked_cast<uint32_t>(sizeof(PP_VideoProfileDescription)));
+
+ if (!ptr) {
+ RunCallback(&get_supported_profiles_callback_, PP_ERROR_FAILED);
+ return;
+ }
+
+ if (profiles.size() > 0)
+ memcpy(ptr, &profiles[0],
+ profiles.size() * sizeof(PP_VideoProfileDescription));
+ RunCallback(&get_supported_profiles_callback_, PP_OK);
+}
+
+void VideoEncoderResource::OnPluginMsgInitializeReply(
+ const ResourceMessageReplyParams& params,
+ uint32_t input_frame_count,
+ const PP_Size& input_coded_size) {
+ DCHECK(!initialized_);
+
+ encoder_last_error_ = params.result();
+ if (!encoder_last_error_)
+ initialized_ = true;
+
+ input_frame_count_ = input_frame_count;
+ input_coded_size_ = input_coded_size;
+
+ RunCallback(&initialize_callback_, encoder_last_error_);
+}
+
+void VideoEncoderResource::OnPluginMsgGetVideoFramesReply(
+ const ResourceMessageReplyParams& params,
+ uint32_t frame_count,
+ uint32_t frame_length,
+ const PP_Size& frame_size) {
+ int32_t error = params.result();
+ if (error) {
+ NotifyError(error);
+ return;
+ }
+
+ base::SharedMemoryHandle buffer_handle;
+ params.TakeSharedMemoryHandleAtIndex(0, &buffer_handle);
+
+ if (!buffer_manager_.SetBuffers(
+ frame_count, frame_length,
+ make_scoped_ptr(new base::SharedMemory(buffer_handle, false)),
+ true)) {
+ NotifyError(PP_ERROR_FAILED);
+ return;
+ }
+
+ if (TrackedCallback::IsPending(get_video_frame_callback_))
+ TryWriteVideoFrame();
+}
+
+void VideoEncoderResource::OnPluginMsgEncodeReply(
+ PP_Resource video_frame,
+ const ResourceMessageReplyParams& params,
+ uint32_t frame_id) {
+ DCHECK_NE(encode_callbacks_.size(), 0U);
+ encoder_last_error_ = params.result();
+
+ EncodeMap::iterator it = encode_callbacks_.find(video_frame);
+ DCHECK(encode_callbacks_.end() != it);
+
+ scoped_refptr<TrackedCallback> callback = it->second;
+ encode_callbacks_.erase(it);
+ RunCallback(&callback, encoder_last_error_);
+
+ buffer_manager_.EnqueueBuffer(frame_id);
+ // If the plugin is waiting for a video frame, we can give the one
+ // that just became available again.
+ if (TrackedCallback::IsPending(get_video_frame_callback_))
+ TryWriteVideoFrame();
+}
+
+void VideoEncoderResource::OnPluginMsgBitstreamBuffers(
+ const ResourceMessageReplyParams& params,
+ uint32_t buffer_length) {
+ std::vector<base::SharedMemoryHandle> shm_handles;
+ params.TakeAllSharedMemoryHandles(&shm_handles);
+ if (shm_handles.size() == 0) {
+ NotifyError(PP_ERROR_FAILED);
+ return;
+ }
+
+ for (uint32_t i = 0; i < shm_handles.size(); ++i) {
+ scoped_ptr<base::SharedMemory> shm(
+ new base::SharedMemory(shm_handles[i], true));
+ CHECK(shm->Map(buffer_length));
+
+ ShmBuffer* buffer = new ShmBuffer(i, shm.Pass());
+ shm_buffers_.push_back(buffer);
+ bitstream_buffer_map_.insert(
+ std::make_pair(buffer->shm->memory(), buffer->id));
+ }
+}
+
+void VideoEncoderResource::OnPluginMsgBitstreamBufferReady(
+ const ResourceMessageReplyParams& params,
+ uint32_t buffer_id,
+ uint32_t buffer_size,
+ bool key_frame) {
+ available_bitstream_buffers_.push_back(
+ BitstreamBuffer(buffer_id, buffer_size, key_frame));
+
+ if (TrackedCallback::IsPending(get_bitstream_buffer_callback_)) {
+ BitstreamBuffer buffer(available_bitstream_buffers_.front());
+ available_bitstream_buffers_.pop_front();
+ WriteBitstreamBuffer(buffer);
+ }
+}
+
+void VideoEncoderResource::OnPluginMsgNotifyError(
+ const ResourceMessageReplyParams& params,
+ int32_t error) {
+ NotifyError(error);
+}
+
+void VideoEncoderResource::NotifyError(int32_t error) {
+ encoder_last_error_ = error;
+ RunCallback(&get_supported_profiles_callback_, error);
+ RunCallback(&initialize_callback_, error);
+ RunCallback(&get_video_frame_callback_, error);
+ get_video_frame_data_ = nullptr;
+ RunCallback(&get_bitstream_buffer_callback_, error);
+ get_bitstream_buffer_data_ = nullptr;
+ for (EncodeMap::iterator it = encode_callbacks_.begin();
+ it != encode_callbacks_.end(); ++it) {
+ scoped_refptr<TrackedCallback> callback = it->second;
+ RunCallback(&callback, error);
+ }
+ encode_callbacks_.clear();
+}
+
+void VideoEncoderResource::TryWriteVideoFrame() {
+ DCHECK(TrackedCallback::IsPending(get_video_frame_callback_));
+
+ int32_t frame_id = buffer_manager_.DequeueBuffer();
+ if (frame_id < 0)
+ return;
+
+ scoped_refptr<VideoFrameResource> resource = new VideoFrameResource(
+ pp_instance(), frame_id, buffer_manager_.GetBufferPointer(frame_id));
+ video_frames_.insert(
+ VideoFrameMap::value_type(resource->pp_resource(), resource));
+
+ *get_video_frame_data_ = resource->GetReference();
+ get_video_frame_data_ = nullptr;
+ RunCallback(&get_video_frame_callback_, PP_OK);
+}
+
+void VideoEncoderResource::WriteBitstreamBuffer(const BitstreamBuffer& buffer) {
+ DCHECK_LT(buffer.id, shm_buffers_.size());
+
+ get_bitstream_buffer_data_->size = buffer.size;
+ get_bitstream_buffer_data_->buffer = shm_buffers_[buffer.id]->shm->memory();
+ get_bitstream_buffer_data_->key_frame = PP_FromBool(buffer.key_frame);
+ get_bitstream_buffer_data_ = nullptr;
+ RunCallback(&get_bitstream_buffer_callback_, PP_OK);
+}
+
+void VideoEncoderResource::ReleaseFrames() {
+ for (VideoFrameMap::iterator it = video_frames_.begin();
+ it != video_frames_.end(); ++it) {
+ it->second->Invalidate();
+ it->second = nullptr;
+ }
+ video_frames_.clear();
}
} // namespace proxy
diff --git a/ppapi/proxy/video_encoder_resource.h b/ppapi/proxy/video_encoder_resource.h
index 585d5c1..3b46759 100644
--- a/ppapi/proxy/video_encoder_resource.h
+++ b/ppapi/proxy/video_encoder_resource.h
@@ -5,21 +5,34 @@
#ifndef PPAPI_PROXY_VIDEO_ENCODER_RESOURCE_H_
#define PPAPI_PROXY_VIDEO_ENCODER_RESOURCE_H_
+#include <deque>
+
#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
#include "ppapi/proxy/connection.h"
#include "ppapi/proxy/plugin_resource.h"
+#include "ppapi/shared_impl/media_stream_buffer_manager.h"
#include "ppapi/shared_impl/resource.h"
#include "ppapi/thunk/ppb_video_encoder_api.h"
+namespace base {
+class SharedMemory;
+}
+
namespace ppapi {
class TrackedCallback;
namespace proxy {
+class SerializedHandle;
+class VideoFrameResource;
+
class PPAPI_PROXY_EXPORT VideoEncoderResource
: public PluginResource,
- public thunk::PPB_VideoEncoder_API {
+ public thunk::PPB_VideoEncoder_API,
+ public ppapi::MediaStreamBufferManager::Delegate {
public:
VideoEncoderResource(Connection connection, PP_Instance instance);
~VideoEncoderResource() override;
@@ -27,6 +40,26 @@ class PPAPI_PROXY_EXPORT VideoEncoderResource
thunk::PPB_VideoEncoder_API* AsPPB_VideoEncoder_API() override;
private:
+ struct ShmBuffer {
+ ShmBuffer(uint32_t id, scoped_ptr<base::SharedMemory> shm);
+ ~ShmBuffer();
+
+ // Index of the buffer in the ScopedVector. Buffers have the same id in
+ // the plugin and the host.
+ uint32_t id;
+ scoped_ptr<base::SharedMemory> shm;
+ };
+
+ struct BitstreamBuffer {
+ BitstreamBuffer(uint32_t id, uint32_t size, bool key_frame);
+ ~BitstreamBuffer();
+
+ // Index of the buffer in the ScopedVector. Same as ShmBuffer::id.
+ uint32_t id;
+ uint32_t size;
+ bool key_frame;
+ };
+
// PPB_VideoEncoder_API implementation.
int32_t GetSupportedProfiles(
const PP_ArrayOutput& output,
@@ -53,6 +86,72 @@ class PPAPI_PROXY_EXPORT VideoEncoderResource
uint32_t framerate) override;
void Close() override;
+ // PluginResource implementation.
+ void OnReplyReceived(const ResourceMessageReplyParams& params,
+ const IPC::Message& msg) override;
+
+ // Reply message handlers for operations that are done in the host.
+ void OnPluginMsgGetSupportedProfilesReply(
+ const PP_ArrayOutput& output,
+ const ResourceMessageReplyParams& params,
+ const std::vector<PP_VideoProfileDescription>& profiles);
+ void OnPluginMsgInitializeReply(const ResourceMessageReplyParams& params,
+ uint32_t input_frame_count,
+ const PP_Size& input_coded_size);
+ void OnPluginMsgGetVideoFramesReply(const ResourceMessageReplyParams& params,
+ uint32_t frame_count,
+ uint32_t frame_length,
+ const PP_Size& frame_size);
+ void OnPluginMsgEncodeReply(PP_Resource video_frame,
+ const ResourceMessageReplyParams& params,
+ uint32_t frame_id);
+
+ // Unsolicited reply message handlers.
+ void OnPluginMsgBitstreamBuffers(const ResourceMessageReplyParams& params,
+ uint32_t buffer_length);
+ void OnPluginMsgBitstreamBufferReady(const ResourceMessageReplyParams& params,
+ uint32_t buffer_id,
+ uint32_t buffer_size,
+ bool key_frame);
+ void OnPluginMsgNotifyError(const ResourceMessageReplyParams& params,
+ int32_t error);
+
+ // Internal utility functions.
+ void NotifyError(int32_t error);
+ void TryWriteVideoFrame();
+ void WriteBitstreamBuffer(const BitstreamBuffer& buffer);
+ void ReleaseFrames();
+
+ bool initialized_;
+ bool closed_;
+ int32_t encoder_last_error_;
+
+ int32_t input_frame_count_;
+ PP_Size input_coded_size_;
+
+ MediaStreamBufferManager buffer_manager_;
+
+ typedef std::map<PP_Resource, scoped_refptr<VideoFrameResource> >
+ VideoFrameMap;
+ VideoFrameMap video_frames_;
+
+ ScopedVector<ShmBuffer> shm_buffers_;
+
+ std::deque<BitstreamBuffer> available_bitstream_buffers_;
+ typedef std::map<void*, uint32_t> BitstreamBufferMap;
+ BitstreamBufferMap bitstream_buffer_map_;
+
+ scoped_refptr<TrackedCallback> get_supported_profiles_callback_;
+ scoped_refptr<TrackedCallback> initialize_callback_;
+ scoped_refptr<TrackedCallback> get_video_frame_callback_;
+ PP_Resource* get_video_frame_data_;
+
+ typedef std::map<PP_Resource, scoped_refptr<TrackedCallback> > EncodeMap;
+ EncodeMap encode_callbacks_;
+
+ scoped_refptr<TrackedCallback> get_bitstream_buffer_callback_;
+ PP_BitstreamBuffer* get_bitstream_buffer_data_;
+
DISALLOW_COPY_AND_ASSIGN(VideoEncoderResource);
};
diff --git a/ppapi/proxy/video_encoder_resource_unittest.cc b/ppapi/proxy/video_encoder_resource_unittest.cc
new file mode 100644
index 0000000..0adc1ea
--- /dev/null
+++ b/ppapi/proxy/video_encoder_resource_unittest.cc
@@ -0,0 +1,1085 @@
+// Copyright 2015 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/memory/shared_memory.h"
+#include "base/process/process.h"
+#include "base/synchronization/waitable_event.h"
+#include "ppapi/c/pp_codecs.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/ppb_video_encoder.h"
+#include "ppapi/c/ppb_video_frame.h"
+#include "ppapi/proxy/locking_resource_releaser.h"
+#include "ppapi/proxy/plugin_message_filter.h"
+#include "ppapi/proxy/ppapi_message_utils.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/proxy/ppapi_proxy_test.h"
+#include "ppapi/proxy/video_encoder_resource.h"
+#include "ppapi/shared_impl/media_stream_buffer.h"
+#include "ppapi/shared_impl/proxy_lock.h"
+#include "ppapi/thunk/thunk.h"
+
+using ppapi::proxy::ResourceMessageTestSink;
+
+namespace ppapi {
+namespace proxy {
+
+namespace {
+
+class MockCompletionCallback {
+ public:
+ MockCompletionCallback() : called_(false), call_event_(false, false) {}
+
+ bool called() { return called_; }
+ int32_t result() { return result_; }
+
+ void WaitUntilCalled() { call_event_.Wait(); }
+
+ void Reset() {
+ called_ = false;
+ call_event_.Reset();
+ }
+
+ static void Callback(void* user_data, int32_t result) {
+ MockCompletionCallback* that =
+ reinterpret_cast<MockCompletionCallback*>(user_data);
+ that->call_event_.Signal();
+ that->called_ = true;
+ that->result_ = result;
+ }
+
+ private:
+ bool called_;
+ int32_t result_;
+ base::WaitableEvent call_event_;
+};
+
+class VideoEncoderResourceTest : public PluginProxyTest,
+ public MediaStreamBufferManager::Delegate {
+ public:
+ VideoEncoderResourceTest()
+ : encoder_iface_(thunk::GetPPB_VideoEncoder_0_1_Thunk()),
+ video_frames_manager_(this) {}
+ ~VideoEncoderResourceTest() override {}
+
+ const PPB_VideoEncoder_0_1* encoder_iface() const { return encoder_iface_; }
+
+ const uint32_t kBitstreamBufferSize = 4000;
+ const uint32_t kBitstreamBufferCount = 5;
+ const uint32_t kVideoFrameCount = 3;
+ const uint32_t kBitrate = 200000;
+
+ const PP_Size kFrameSize = PP_MakeSize(640, 480);
+
+ void SendReply(const ResourceMessageCallParams& params,
+ int32_t result,
+ const IPC::Message& nested_message) {
+ ResourceMessageReplyParams reply_params(params.pp_resource(),
+ params.sequence());
+ reply_params.set_result(result);
+ PluginMessageFilter::DispatchResourceReplyForTest(reply_params,
+ nested_message);
+ }
+
+ void SendReplyWithHandle(const ResourceMessageCallParams& params,
+ int32_t result,
+ const IPC::Message& nested_message,
+ const SerializedHandle& handle) {
+ ResourceMessageReplyParams reply_params(params.pp_resource(),
+ params.sequence());
+ reply_params.set_result(result);
+ reply_params.AppendHandle(handle);
+ PluginMessageFilter::DispatchResourceReplyForTest(reply_params,
+ nested_message);
+ }
+
+ void SendReplyWithHandles(const ResourceMessageCallParams& params,
+ int32_t result,
+ const IPC::Message& nested_message,
+ const std::vector<SerializedHandle>& handles) {
+ ResourceMessageReplyParams reply_params(params.pp_resource(),
+ params.sequence());
+ reply_params.set_result(result);
+ for (SerializedHandle handle : handles)
+ reply_params.AppendHandle(handle);
+ PluginMessageFilter::DispatchResourceReplyForTest(reply_params,
+ nested_message);
+ }
+
+ PP_Resource CreateEncoder() {
+ PP_Resource result = encoder_iface()->Create(pp_instance());
+ return result;
+ }
+
+ void CreateBitstreamSharedMemory(uint32_t buffer_size, uint32_t nb_buffers) {
+ shared_memory_bitstreams_.clear();
+ for (uint32_t i = 0; i < nb_buffers; ++i) {
+ scoped_ptr<base::SharedMemory> mem(new base::SharedMemory());
+ ASSERT_TRUE(mem->CreateAnonymous(buffer_size));
+ shared_memory_bitstreams_.push_back(mem.Pass());
+ }
+ }
+
+ void CreateVideoFramesSharedMemory(uint32_t frame_length,
+ uint32_t frame_count) {
+ scoped_ptr<base::SharedMemory> shared_memory_frames(
+ new base::SharedMemory());
+ uint32_t buffer_length =
+ frame_length + sizeof(ppapi::MediaStreamBuffer::Video);
+ ASSERT_TRUE(shared_memory_frames->CreateAnonymous(buffer_length *
+ frame_count));
+ ASSERT_TRUE(video_frames_manager_.SetBuffers(frame_count,
+ buffer_length,
+ shared_memory_frames.Pass(),
+ true));
+ for (int32_t i = 0; i < video_frames_manager_.number_of_buffers(); ++i) {
+ ppapi::MediaStreamBuffer::Video* buffer =
+ &(video_frames_manager_.GetBufferPointer(i)->video);
+ buffer->header.size = buffer_length;
+ buffer->header.type = ppapi::MediaStreamBuffer::TYPE_VIDEO;
+ buffer->format = PP_VIDEOFRAME_FORMAT_I420;
+ buffer->size = kFrameSize;
+ buffer->data_size = frame_length;
+ }
+ }
+
+ PP_Resource CreateAndInitializeEncoder() {
+ PP_Resource encoder = CreateEncoder();
+ PP_Size size = kFrameSize;
+ MockCompletionCallback cb;
+ int32_t result = encoder_iface()->Initialize(
+ encoder, PP_VIDEOFRAME_FORMAT_I420, &size, PP_VIDEOPROFILE_H264MAIN,
+ kBitrate, PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ if (result != PP_OK_COMPLETIONPENDING)
+ return 0;
+ ResourceMessageCallParams params;
+ IPC::Message msg;
+ if (!sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_VideoEncoder_Initialize::ID, &params, &msg))
+ return 0;
+ sink().ClearMessages();
+
+ SendInitializeReply(params, PP_OK, kVideoFrameCount, kFrameSize);
+ CreateBitstreamSharedMemory(kBitstreamBufferSize, kBitstreamBufferCount);
+ SendBitstreamBuffers(params, kBitstreamBufferSize);
+
+ if (!cb.called() || cb.result() != PP_OK)
+ return 0;
+
+ return encoder;
+ }
+
+ int32_t CallGetFramesRequired(PP_Resource pp_encoder) {
+ return encoder_iface()->GetFramesRequired(pp_encoder);
+ }
+
+ int32_t CallGetFrameCodedSize(PP_Resource pp_encoder, PP_Size* coded_size) {
+ return encoder_iface()->GetFrameCodedSize(pp_encoder, coded_size);
+ }
+
+ int32_t CallGetVideoFrame(PP_Resource pp_encoder,
+ PP_Resource* video_frame,
+ MockCompletionCallback* cb) {
+ return encoder_iface()->GetVideoFrame(
+ pp_encoder, video_frame, PP_MakeOptionalCompletionCallback(
+ &MockCompletionCallback::Callback, cb));
+ }
+
+ int32_t CallFirstGetVideoFrame(PP_Resource pp_encoder,
+ PP_Resource* video_frame,
+ MockCompletionCallback* cb) {
+ int32_t result = encoder_iface()->GetVideoFrame(
+ pp_encoder, video_frame, PP_MakeOptionalCompletionCallback(
+ &MockCompletionCallback::Callback, cb));
+ if (result != PP_OK_COMPLETIONPENDING)
+ return result;
+
+ ResourceMessageCallParams params;
+ CheckGetVideoFramesMsg(&params);
+
+ uint32_t frame_length = kFrameSize.width * kFrameSize.height * 2;
+ CreateVideoFramesSharedMemory(frame_length, kVideoFrameCount);
+ SendGetVideoFramesReply(params, kVideoFrameCount, frame_length, kFrameSize);
+
+ return result;
+ }
+
+ int32_t CallEncode(PP_Resource pp_encoder,
+ PP_Resource video_frame,
+ PP_Bool force_keyframe,
+ MockCompletionCallback* cb) {
+ return encoder_iface()->Encode(pp_encoder, video_frame, force_keyframe,
+ PP_MakeOptionalCompletionCallback(
+ &MockCompletionCallback::Callback, cb));
+ }
+
+ int32_t CallCompleteEncode(PP_Resource pp_encoder,
+ PP_Resource video_frame,
+ PP_Bool force_keyframe,
+ MockCompletionCallback* cb) {
+ int32_t result =
+ encoder_iface()->Encode(pp_encoder, video_frame, force_keyframe,
+ PP_MakeOptionalCompletionCallback(
+ &MockCompletionCallback::Callback, cb));
+ if (result != PP_OK_COMPLETIONPENDING)
+ return result;
+
+ ResourceMessageCallParams params;
+ uint32_t frame_id;
+ bool forced_keyframe;
+ if (!CheckEncodeMsg(&params, &frame_id, &forced_keyframe))
+ return PP_ERROR_FAILED;
+
+ SendEncodeReply(params, frame_id);
+
+ return result;
+ }
+
+ int32_t CallGetBitstreamBuffer(PP_Resource pp_encoder,
+ PP_BitstreamBuffer* bitstream_buffer,
+ MockCompletionCallback* cb) {
+ return encoder_iface()->GetBitstreamBuffer(
+ pp_encoder, bitstream_buffer,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ cb));
+ }
+
+ void CallRecycleBitstreamBuffer(PP_Resource pp_encoder,
+ const PP_BitstreamBuffer& buffer) {
+ encoder_iface()->RecycleBitstreamBuffer(pp_encoder, &buffer);
+ }
+
+ void CallRequestEncodingParametersChange(PP_Resource pp_encoder,
+ uint32_t bitrate,
+ uint32_t framerate) {
+ encoder_iface()->RequestEncodingParametersChange(pp_encoder, bitrate,
+ framerate);
+ }
+
+ void CallClose(PP_Resource pp_encoder) {
+ encoder_iface()->Close(pp_encoder);
+ }
+
+ void SendGetSupportedProfilesReply(
+ const ResourceMessageCallParams& params,
+ const std::vector<PP_VideoProfileDescription>& profiles) {
+ SendReply(params, PP_OK,
+ PpapiPluginMsg_VideoEncoder_GetSupportedProfilesReply(profiles));
+ }
+
+ void SendInitializeReply(const ResourceMessageCallParams& params,
+ int32_t success,
+ uint32_t input_frame_count,
+ const PP_Size& input_coded_size) {
+ SendReply(params, success, PpapiPluginMsg_VideoEncoder_InitializeReply(
+ input_frame_count, input_coded_size));
+ }
+
+ void SendBitstreamBuffers(const ResourceMessageCallParams& params,
+ uint32_t buffer_length) {
+ std::vector<SerializedHandle> handles;
+ for (base::SharedMemory* mem : shared_memory_bitstreams_) {
+ ASSERT_EQ(mem->requested_size(), buffer_length);
+ base::SharedMemoryHandle handle;
+
+ ASSERT_TRUE(
+ mem->ShareToProcess(base::Process::Current().Handle(), &handle));
+ handles.push_back(SerializedHandle(handle, buffer_length));
+ }
+ SendReplyWithHandles(
+ params, PP_OK,
+ PpapiPluginMsg_VideoEncoder_BitstreamBuffers(buffer_length), handles);
+ }
+
+ void SendGetVideoFramesReply(const ResourceMessageCallParams& params,
+ uint32_t frame_count,
+ uint32_t frame_length,
+ const PP_Size& size) {
+ base::SharedMemoryHandle handle;
+ ASSERT_TRUE(video_frames_manager_.shm()->ShareToProcess(
+ base::Process::Current().Handle(), &handle));
+ SendReplyWithHandle(
+ params, PP_OK, PpapiPluginMsg_VideoEncoder_GetVideoFramesReply(
+ frame_count,
+ frame_length + sizeof(MediaStreamBuffer::Video),
+ size),
+ SerializedHandle(
+ handle,
+ static_cast<uint32_t>(
+ video_frames_manager_.shm()->requested_size())));
+ }
+
+ void SendEncodeReply(const ResourceMessageCallParams& params,
+ uint32_t frame_id) {
+ SendReply(params, PP_OK, PpapiPluginMsg_VideoEncoder_EncodeReply(frame_id));
+ }
+
+ void SendBitstreamBufferReady(const ResourceMessageCallParams& params,
+ uint32_t buffer_id,
+ uint32_t buffer_size,
+ bool keyframe) {
+ SendReply(params, PP_OK,
+ PpapiPluginMsg_VideoEncoder_BitstreamBufferReady(
+ buffer_id, buffer_size, PP_FromBool(keyframe)));
+ }
+
+ void SendNotifyError(const ResourceMessageCallParams& params, int32_t error) {
+ SendReply(params, PP_OK, PpapiPluginMsg_VideoEncoder_NotifyError(error));
+ }
+
+ bool CheckGetSupportedProfilesMsg(ResourceMessageCallParams* params) {
+ IPC::Message msg;
+ return sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_VideoEncoder_GetSupportedProfiles::ID, params, &msg);
+ }
+
+ bool CheckInitializeMsg(ResourceMessageCallParams* params,
+ PP_VideoFrame_Format* input_format,
+ struct PP_Size* input_visible_size,
+ PP_VideoProfile* output_profile,
+ uint32_t* bitrate,
+ PP_HardwareAcceleration* acceleration) {
+ IPC::Message msg;
+ if (!sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_VideoEncoder_Initialize::ID, params, &msg))
+ return false;
+ sink().ClearMessages();
+ return UnpackMessage<PpapiHostMsg_VideoEncoder_Initialize>(
+ msg, input_format, input_visible_size, output_profile, bitrate,
+ acceleration);
+ }
+
+ bool CheckGetVideoFramesMsg(ResourceMessageCallParams* params) {
+ IPC::Message msg;
+ if (!sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_VideoEncoder_GetVideoFrames::ID, params, &msg))
+ return false;
+ sink().ClearMessages();
+ return true;
+ }
+
+ bool CheckEncodeMsg(ResourceMessageCallParams* params,
+ uint32_t* frame_id,
+ bool* keyframe) {
+ IPC::Message msg;
+ if (!sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_VideoEncoder_Encode::ID, params, &msg))
+ return false;
+ sink().ClearMessages();
+ return UnpackMessage<PpapiHostMsg_VideoEncoder_Encode>(msg, frame_id,
+ keyframe);
+ }
+
+ bool CheckRecycleBitstreamBufferMsg(ResourceMessageCallParams* params,
+ uint32_t* buffer_id) {
+ IPC::Message msg;
+ if (!sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer::ID, params, &msg))
+ return false;
+ sink().ClearMessages();
+ return UnpackMessage<PpapiHostMsg_VideoEncoder_RecycleBitstreamBuffer>(
+ msg, buffer_id);
+ }
+
+ bool CheckRequestEncodingParametersChangeMsg(
+ ResourceMessageCallParams* params,
+ uint32_t* bitrate,
+ uint32_t* framerate) {
+ IPC::Message msg;
+ if (!sink().GetFirstResourceCallMatching(
+ PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange::ID,
+ params, &msg))
+ return false;
+ sink().ClearMessages();
+ return UnpackMessage<
+ PpapiHostMsg_VideoEncoder_RequestEncodingParametersChange>(msg, bitrate,
+ framerate);
+ }
+
+ bool CheckIsVideoFrame(PP_Resource video_frame) {
+ return thunk::GetPPB_VideoFrame_0_1_Thunk()->IsVideoFrame(video_frame);
+ }
+
+ bool CheckIsVideoFrameValid(PP_Resource video_frame) {
+ PP_Size frame_size;
+ return thunk::GetPPB_VideoFrame_0_1_Thunk()->GetSize(
+ video_frame, &frame_size) == PP_TRUE;
+ }
+
+ private:
+ // MediaStreamBufferManager::Delegate:
+ void OnNewBufferEnqueued() override {}
+
+ const PPB_VideoEncoder_0_1* encoder_iface_;
+
+ ScopedVector<base::SharedMemory> shared_memory_bitstreams_;
+
+ MediaStreamBufferManager video_frames_manager_;
+};
+
+void* ForwardUserData(void* user_data,
+ uint32_t element_count,
+ uint32_t element_size) {
+ return user_data;
+}
+
+} // namespace
+
+TEST_F(VideoEncoderResourceTest, GetSupportedProfiles) {
+ // Verifies that GetSupportedProfiles calls into the renderer and
+ // the we get the right results back.
+ {
+ LockingResourceReleaser encoder(CreateEncoder());
+ PP_VideoProfileDescription profiles[2];
+ PP_ArrayOutput output;
+ output.user_data = &profiles[0];
+ output.GetDataBuffer = ForwardUserData;
+ ResourceMessageCallParams params;
+ MockCompletionCallback cb;
+ int32_t result = encoder_iface()->GetSupportedProfiles(
+ encoder.get(), output, PP_MakeOptionalCompletionCallback(
+ &MockCompletionCallback::Callback, &cb));
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+ ASSERT_TRUE(CheckGetSupportedProfilesMsg(&params));
+
+ std::vector<PP_VideoProfileDescription> profiles_response;
+ PP_VideoProfileDescription profile;
+ profile.profile = PP_VIDEOPROFILE_H264MAIN;
+ profile.max_resolution.width = 1920;
+ profile.max_resolution.height = 1080;
+ profile.max_framerate_numerator = 30;
+ profile.max_framerate_denominator = 1;
+ profile.acceleration = PP_HARDWAREACCELERATION_ONLY;
+ profiles_response.push_back(profile);
+ profile.profile = PP_VIDEOPROFILE_VP8_ANY;
+ profile.max_resolution.width = 1920;
+ profile.max_resolution.height = 1080;
+ profile.max_framerate_numerator = 30;
+ profile.max_framerate_denominator = 1;
+ profile.acceleration = PP_HARDWAREACCELERATION_NONE;
+ profiles_response.push_back(profile);
+
+ SendGetSupportedProfilesReply(params, profiles_response);
+ ASSERT_EQ(PP_OK, cb.result());
+
+ ASSERT_EQ(2U, profiles_response.size());
+ ASSERT_EQ(0, memcmp(&profiles[0], &profiles_response[0],
+ sizeof(PP_VideoProfileDescription) * 2));
+ }
+}
+
+TEST_F(VideoEncoderResourceTest, InitializeFailure) {
+ {
+ // Verify the initialize callback is called in case of failure.
+ LockingResourceReleaser encoder(CreateEncoder());
+ ResourceMessageCallParams params;
+ PP_Size size = kFrameSize;
+ MockCompletionCallback cb;
+ int32_t result = encoder_iface()->Initialize(
+ encoder.get(), PP_VIDEOFRAME_FORMAT_BGRA, &size,
+ PP_VIDEOPROFILE_H264MAIN, kBitrate,
+ PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ PP_VideoFrame_Format input_format;
+ PP_Size input_visible_size;
+ PP_VideoProfile output_profile;
+ uint32_t bitrate;
+ PP_HardwareAcceleration acceleration;
+ ASSERT_TRUE(CheckInitializeMsg(&params, &input_format, &input_visible_size,
+ &output_profile, &bitrate, &acceleration));
+ ASSERT_EQ(PP_VIDEOFRAME_FORMAT_BGRA, input_format);
+ ASSERT_EQ(size.width, input_visible_size.width);
+ ASSERT_EQ(size.height, input_visible_size.height);
+ ASSERT_EQ(kBitrate, bitrate);
+ ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile);
+ ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration);
+
+ SendInitializeReply(params, PP_ERROR_NOTSUPPORTED, kVideoFrameCount,
+ kFrameSize);
+ ASSERT_TRUE(cb.called());
+ ASSERT_EQ(PP_ERROR_NOTSUPPORTED, cb.result());
+ }
+ {
+ // Verify the initialize callback is called in case of error
+ // notification.
+ LockingResourceReleaser encoder(CreateEncoder());
+ ResourceMessageCallParams params;
+ PP_Size size = kFrameSize;
+ MockCompletionCallback cb;
+ int32_t result = encoder_iface()->Initialize(
+ encoder.get(), PP_VIDEOFRAME_FORMAT_BGRA, &size,
+ PP_VIDEOPROFILE_H264MAIN, kBitrate,
+ PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ PP_VideoFrame_Format input_format;
+ PP_Size input_visible_size;
+ PP_VideoProfile output_profile;
+ uint32_t bitrate;
+ PP_HardwareAcceleration acceleration;
+ ASSERT_TRUE(CheckInitializeMsg(&params, &input_format, &input_visible_size,
+ &output_profile, &bitrate, &acceleration));
+ ASSERT_EQ(PP_VIDEOFRAME_FORMAT_BGRA, input_format);
+ ASSERT_EQ(kFrameSize.width, input_visible_size.width);
+ ASSERT_EQ(kFrameSize.height, input_visible_size.height);
+ ASSERT_EQ(kBitrate, bitrate);
+ ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile);
+ ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration);
+
+ ResourceMessageCallParams error_params(encoder.get(), 0);
+ SendNotifyError(error_params, PP_ERROR_FAILED);
+ ASSERT_TRUE(cb.called());
+ ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+ }
+ {
+ // Verify that calling initialize twice fails the second time if
+ // we haven't received a response yet.
+ LockingResourceReleaser encoder(CreateEncoder());
+ ResourceMessageCallParams params;
+ PP_Size size = kFrameSize;
+ MockCompletionCallback cb;
+ int32_t result = encoder_iface()->Initialize(
+ encoder.get(), PP_VIDEOFRAME_FORMAT_BGRA, &size,
+ PP_VIDEOPROFILE_H264MAIN, kBitrate,
+ PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ PP_VideoFrame_Format input_format;
+ PP_Size input_visible_size;
+ PP_VideoProfile output_profile;
+ uint32_t bitrate;
+ PP_HardwareAcceleration acceleration;
+ ASSERT_TRUE(CheckInitializeMsg(&params, &input_format, &input_visible_size,
+ &output_profile, &bitrate, &acceleration));
+ ASSERT_EQ(PP_VIDEOFRAME_FORMAT_BGRA, input_format);
+ ASSERT_EQ(size.width, input_visible_size.width);
+ ASSERT_EQ(size.height, input_visible_size.height);
+ ASSERT_EQ(kBitrate, bitrate);
+ ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile);
+ ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration);
+
+ result = encoder_iface()->Initialize(
+ encoder.get(), PP_VIDEOFRAME_FORMAT_BGRA, &size,
+ PP_VIDEOPROFILE_H264MAIN, kBitrate,
+ PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ ASSERT_EQ(PP_ERROR_INPROGRESS, result);
+
+ ResourceMessageCallParams error_params(encoder.get(), 0);
+ SendNotifyError(error_params, PP_ERROR_FAILED);
+ ASSERT_TRUE(cb.called());
+ ASSERT_EQ(PP_ERROR_FAILED, cb.result());
+ }
+}
+
+TEST_F(VideoEncoderResourceTest, InitializeSuccess) {
+ {
+ // Verify the initialize callback is called when initialization is
+ // successfull.
+ LockingResourceReleaser encoder(CreateEncoder());
+ ResourceMessageCallParams params;
+ PP_Size size = kFrameSize;
+ const uint32_t kBitrate = 420000;
+ MockCompletionCallback cb;
+ int32_t result = encoder_iface()->Initialize(
+ encoder.get(), PP_VIDEOFRAME_FORMAT_I420, &size,
+ PP_VIDEOPROFILE_H264MAIN, kBitrate,
+ PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ PP_VideoFrame_Format input_format;
+ PP_Size input_visible_size;
+ PP_VideoProfile output_profile;
+ uint32_t bitrate;
+ PP_HardwareAcceleration acceleration;
+ ASSERT_TRUE(CheckInitializeMsg(&params, &input_format, &input_visible_size,
+ &output_profile, &bitrate, &acceleration));
+ ASSERT_EQ(PP_VIDEOFRAME_FORMAT_I420, input_format);
+ ASSERT_EQ(kFrameSize.width, input_visible_size.width);
+ ASSERT_EQ(kFrameSize.height, input_visible_size.height);
+ ASSERT_EQ(kBitrate, bitrate);
+ ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile);
+ ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration);
+
+ SendInitializeReply(params, PP_OK, kVideoFrameCount, kFrameSize);
+
+ ASSERT_TRUE(cb.called());
+ ASSERT_EQ(PP_OK, cb.result());
+ }
+ {
+ // Verify that calling initialize a second time, after it already
+ // succeeded, fails.
+ LockingResourceReleaser encoder(CreateEncoder());
+ ResourceMessageCallParams params;
+ PP_Size size = kFrameSize;
+ const uint32_t kBitrate = 420000;
+ MockCompletionCallback cb;
+ int32_t result = encoder_iface()->Initialize(
+ encoder.get(), PP_VIDEOFRAME_FORMAT_I420, &size,
+ PP_VIDEOPROFILE_H264MAIN, kBitrate,
+ PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ PP_VideoFrame_Format input_format;
+ PP_Size input_visible_size;
+ PP_VideoProfile output_profile;
+ uint32_t bitrate;
+ PP_HardwareAcceleration acceleration;
+ ASSERT_TRUE(CheckInitializeMsg(&params, &input_format, &input_visible_size,
+ &output_profile, &bitrate, &acceleration));
+ ASSERT_EQ(PP_VIDEOFRAME_FORMAT_I420, input_format);
+ ASSERT_EQ(kFrameSize.width, input_visible_size.width);
+ ASSERT_EQ(kFrameSize.height, input_visible_size.height);
+ ASSERT_EQ(kBitrate, bitrate);
+ ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile);
+ ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration);
+
+ SendInitializeReply(params, PP_OK, kVideoFrameCount, kFrameSize);
+
+ ASSERT_TRUE(cb.called());
+ ASSERT_EQ(PP_OK, cb.result());
+
+ result = encoder_iface()->Initialize(
+ encoder.get(), PP_VIDEOFRAME_FORMAT_I420, &size,
+ PP_VIDEOPROFILE_H264MAIN, kBitrate,
+ PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ ASSERT_EQ(PP_ERROR_FAILED, result);
+ }
+ {
+ // Verify the sending the bitstream buffers details makes them
+ // available through the API. the right values.
+ LockingResourceReleaser encoder(CreateEncoder());
+ ResourceMessageCallParams params;
+ PP_Size size = kFrameSize;
+ const uint32_t kBitrate = 420000;
+ MockCompletionCallback cb;
+ int32_t result = encoder_iface()->Initialize(
+ encoder.get(), PP_VIDEOFRAME_FORMAT_I420, &size,
+ PP_VIDEOPROFILE_H264MAIN, kBitrate,
+ PP_HARDWAREACCELERATION_WITHFALLBACK,
+ PP_MakeOptionalCompletionCallback(&MockCompletionCallback::Callback,
+ &cb));
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
+
+ PP_VideoFrame_Format input_format;
+ PP_Size input_visible_size;
+ PP_VideoProfile output_profile;
+ uint32_t bitrate;
+ PP_HardwareAcceleration acceleration;
+ ASSERT_TRUE(CheckInitializeMsg(&params, &input_format, &input_visible_size,
+ &output_profile, &bitrate, &acceleration));
+ ASSERT_EQ(PP_VIDEOFRAME_FORMAT_I420, input_format);
+ ASSERT_EQ(kFrameSize.width, input_visible_size.width);
+ ASSERT_EQ(kFrameSize.height, input_visible_size.height);
+ ASSERT_EQ(kBitrate, bitrate);
+ ASSERT_EQ(PP_VIDEOPROFILE_H264MAIN, output_profile);
+ ASSERT_EQ(PP_HARDWAREACCELERATION_WITHFALLBACK, acceleration);
+
+ SendInitializeReply(params, PP_OK, kVideoFrameCount, kFrameSize);
+
+ ASSERT_TRUE(cb.called());
+ ASSERT_EQ(PP_OK, cb.result());
+
+ PP_Size coded_size;
+ ASSERT_EQ(PP_OK, CallGetFrameCodedSize(encoder.get(), &coded_size));
+ ASSERT_EQ(kFrameSize.width, coded_size.width);
+ ASSERT_EQ(kFrameSize.height, coded_size.height);
+ ASSERT_EQ(static_cast<int32_t>(kVideoFrameCount),
+ CallGetFramesRequired(encoder.get()));
+ }
+}
+
+TEST_F(VideoEncoderResourceTest, Uninitialized) {
+ // Operations on uninitialized encoders should fail.
+ LockingResourceReleaser encoder(CreateEncoder());
+
+ ASSERT_EQ(PP_ERROR_FAILED, CallGetFramesRequired(encoder.get()));
+
+ PP_Size size;
+ ASSERT_EQ(PP_ERROR_FAILED, CallGetFrameCodedSize(encoder.get(), &size));
+
+ MockCompletionCallback uncalled_cb;
+ PP_Resource video_frame = 0;
+ ASSERT_EQ(PP_ERROR_FAILED,
+ CallGetVideoFrame(encoder.get(), &video_frame, &uncalled_cb));
+ ASSERT_FALSE(uncalled_cb.called());
+ ASSERT_EQ(0, video_frame);
+
+ ASSERT_EQ(PP_ERROR_FAILED,
+ CallEncode(encoder.get(), video_frame, PP_FALSE, &uncalled_cb));
+ ASSERT_FALSE(uncalled_cb.called());
+
+ PP_BitstreamBuffer bitstream_buffer;
+ ASSERT_EQ(
+ PP_ERROR_FAILED,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer, &uncalled_cb));
+ ASSERT_FALSE(uncalled_cb.called());
+
+ ResourceMessageCallParams params;
+ uint32_t buffer_id;
+ CallRecycleBitstreamBuffer(encoder.get(), bitstream_buffer);
+ ASSERT_FALSE(CheckRecycleBitstreamBufferMsg(&params, &buffer_id));
+
+ uint32_t bitrate, framerate;
+ CallRequestEncodingParametersChange(encoder.get(), 0, 0);
+ ASSERT_FALSE(
+ CheckRequestEncodingParametersChangeMsg(&params, &bitrate, &framerate));
+}
+
+TEST_F(VideoEncoderResourceTest, InitializeAndGetVideoFrame) {
+ // Verify that we can pull the right number of video frames before
+ // the proxy makes us wait.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+ ResourceMessageCallParams params;
+ std::vector<PP_Resource> video_frames;
+ MockCompletionCallback get_frame_cb;
+
+ video_frames.resize(kVideoFrameCount + 1);
+
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetVideoFrame(encoder.get(), &video_frames[0], &get_frame_cb));
+ ASSERT_FALSE(get_frame_cb.called());
+ ASSERT_TRUE(CheckGetVideoFramesMsg(&params));
+
+ uint32_t frame_length = kFrameSize.width * kFrameSize.height * 2;
+ CreateVideoFramesSharedMemory(frame_length, kVideoFrameCount);
+ SendGetVideoFramesReply(params, kVideoFrameCount, frame_length, kFrameSize);
+
+ for (uint32_t i = 1; i < kVideoFrameCount; ++i) {
+ get_frame_cb.Reset();
+ ASSERT_EQ(
+ PP_OK_COMPLETIONPENDING,
+ CallGetVideoFrame(encoder.get(), &video_frames[i], &get_frame_cb));
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_OK, get_frame_cb.result());
+ ASSERT_TRUE(CheckIsVideoFrame(video_frames[i]));
+ }
+
+ get_frame_cb.Reset();
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetVideoFrame(encoder.get(), &video_frames[kVideoFrameCount],
+ &get_frame_cb));
+ ASSERT_FALSE(get_frame_cb.called());
+
+ MockCompletionCallback get_frame_fail_cb;
+ ASSERT_EQ(PP_ERROR_INPROGRESS,
+ CallGetVideoFrame(encoder.get(), &video_frames[kVideoFrameCount],
+ &get_frame_fail_cb));
+ ASSERT_FALSE(get_frame_fail_cb.called());
+
+ // Unblock the GetVideoFrame callback by freeing up a frame.
+ MockCompletionCallback encode_cb;
+ ASSERT_EQ(
+ PP_OK_COMPLETIONPENDING,
+ CallCompleteEncode(encoder.get(), video_frames[0], PP_FALSE, &encode_cb));
+ ASSERT_TRUE(encode_cb.called());
+ ASSERT_EQ(PP_OK, encode_cb.result());
+ ASSERT_TRUE(get_frame_cb.called());
+
+ CallClose(encoder.get());
+}
+
+TEST_F(VideoEncoderResourceTest, Encode) {
+ // Check Encode() calls into the renderer.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+ PP_Resource video_frame;
+ MockCompletionCallback get_frame_cb;
+
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallFirstGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb));
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_OK, get_frame_cb.result());
+
+ MockCompletionCallback encode_cb;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallEncode(encoder.get(), video_frame, PP_TRUE, &encode_cb));
+ ASSERT_FALSE(encode_cb.called());
+ ASSERT_FALSE(CheckIsVideoFrameValid(video_frame));
+
+ ResourceMessageCallParams params;
+ uint32_t frame_id;
+ bool force_frame;
+ ASSERT_TRUE(CheckEncodeMsg(&params, &frame_id, &force_frame));
+
+ SendEncodeReply(params, frame_id);
+
+ ASSERT_TRUE(encode_cb.called());
+ ASSERT_EQ(PP_OK, encode_cb.result());
+}
+
+TEST_F(VideoEncoderResourceTest, EncodeAndGetVideoFrame) {
+ // Check the encoding loop works well.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+ ResourceMessageCallParams params;
+ PP_Resource video_frame;
+ MockCompletionCallback get_frame_cb, encode_cb;
+
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallFirstGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb));
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_OK, get_frame_cb.result());
+
+ for (uint32_t i = 1; i < 20 * kVideoFrameCount; ++i) {
+ encode_cb.Reset();
+ ASSERT_EQ(
+ PP_OK_COMPLETIONPENDING,
+ CallCompleteEncode(encoder.get(), video_frame, PP_FALSE, &encode_cb));
+ ASSERT_TRUE(encode_cb.called());
+ ASSERT_EQ(PP_OK, encode_cb.result());
+
+ get_frame_cb.Reset();
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb));
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_OK, get_frame_cb.result());
+ ASSERT_TRUE(CheckIsVideoFrame(video_frame));
+ }
+
+ ASSERT_EQ(
+ PP_OK_COMPLETIONPENDING,
+ CallCompleteEncode(encoder.get(), video_frame, PP_FALSE, &encode_cb));
+ ASSERT_TRUE(encode_cb.called());
+ ASSERT_EQ(PP_OK, encode_cb.result());
+}
+
+TEST_F(VideoEncoderResourceTest, GetBitstreamBuffer) {
+ {
+ // Verify that the GetBitstreamBuffer callback is fired whenever the
+ // renderer signals a buffer is available.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+
+ MockCompletionCallback get_bitstream_buffer_cb;
+ PP_BitstreamBuffer bitstream_buffer;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer,
+ &get_bitstream_buffer_cb));
+ ASSERT_FALSE(get_bitstream_buffer_cb.called());
+
+ ResourceMessageCallParams buffer_params(encoder.get(), 0);
+ SendBitstreamBufferReady(buffer_params, 0, 10, true);
+
+ ASSERT_TRUE(get_bitstream_buffer_cb.called());
+ ASSERT_EQ(PP_OK, get_bitstream_buffer_cb.result());
+ ASSERT_EQ(10U, bitstream_buffer.size);
+ ASSERT_EQ(PP_TRUE, bitstream_buffer.key_frame);
+ }
+ {
+ // Verify that calling GetBitstreamBuffer a second time, while the
+ // first callback hasn't been fired fails.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+
+ MockCompletionCallback get_bitstream_buffer_cb;
+ PP_BitstreamBuffer bitstream_buffer;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer,
+ &get_bitstream_buffer_cb));
+ ASSERT_FALSE(get_bitstream_buffer_cb.called());
+
+ ASSERT_EQ(PP_ERROR_INPROGRESS,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer,
+ &get_bitstream_buffer_cb));
+ ASSERT_FALSE(get_bitstream_buffer_cb.called());
+
+ ResourceMessageCallParams buffer_params(encoder.get(), 0);
+ SendBitstreamBufferReady(buffer_params, 0, 10, true);
+ }
+}
+
+TEST_F(VideoEncoderResourceTest, RecycleBitstreamBuffer) {
+ // Verify that we signal the renderer that a bitstream buffer has been
+ // recycled.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+
+ MockCompletionCallback get_bitstream_buffer_cb;
+ PP_BitstreamBuffer bitstream_buffer;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer,
+ &get_bitstream_buffer_cb));
+ ASSERT_FALSE(get_bitstream_buffer_cb.called());
+
+ ResourceMessageCallParams buffer_params(encoder.get(), 0);
+ SendBitstreamBufferReady(buffer_params, kBitstreamBufferCount - 1, 10, true);
+
+ ASSERT_TRUE(get_bitstream_buffer_cb.called());
+ ASSERT_EQ(PP_OK, get_bitstream_buffer_cb.result());
+
+ CallRecycleBitstreamBuffer(encoder.get(), bitstream_buffer);
+
+ ResourceMessageCallParams recycle_params;
+ uint32_t buffer_id;
+ ASSERT_TRUE(CheckRecycleBitstreamBufferMsg(&recycle_params, &buffer_id));
+ ASSERT_EQ(kBitstreamBufferCount - 1, buffer_id);
+}
+
+TEST_F(VideoEncoderResourceTest, RequestEncodingParametersChange) {
+ // Check encoding parameter changes are correctly sent to the
+ // renderer.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+
+ CallRequestEncodingParametersChange(encoder.get(), 1, 2);
+ ResourceMessageCallParams params;
+ uint32_t bitrate, framerate;
+ ASSERT_TRUE(
+ CheckRequestEncodingParametersChangeMsg(&params, &bitrate, &framerate));
+ ASSERT_EQ(1U, bitrate);
+ ASSERT_EQ(2U, framerate);
+}
+
+TEST_F(VideoEncoderResourceTest, NotifyError) {
+ {
+ // Check an error from the encoder aborts GetVideoFrame and
+ // GetBitstreamBuffer callbacks.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+
+ MockCompletionCallback get_frame_cb;
+ PP_Resource video_frame;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb));
+ ASSERT_FALSE(get_frame_cb.called());
+
+ MockCompletionCallback get_bitstream_buffer_cb;
+ PP_BitstreamBuffer bitstream_buffer;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer,
+ &get_bitstream_buffer_cb));
+
+ ResourceMessageCallParams error_params(encoder.get(), 0);
+ SendNotifyError(error_params, PP_ERROR_FAILED);
+
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_ERROR_FAILED, get_frame_cb.result());
+ ASSERT_TRUE(get_bitstream_buffer_cb.called());
+ ASSERT_EQ(PP_ERROR_FAILED, get_bitstream_buffer_cb.result());
+ }
+ {
+ // Check an error from the encoder aborts Encode and GetBitstreamBuffer
+ // callbacks.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+
+ MockCompletionCallback get_frame_cb, encode_cb1, encode_cb2;
+ PP_Resource video_frame1, video_frame2;
+ ASSERT_EQ(
+ PP_OK_COMPLETIONPENDING,
+ CallFirstGetVideoFrame(encoder.get(), &video_frame1, &get_frame_cb));
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_OK, get_frame_cb.result());
+
+ get_frame_cb.Reset();
+ ASSERT_EQ(
+ PP_OK_COMPLETIONPENDING,
+ CallFirstGetVideoFrame(encoder.get(), &video_frame2, &get_frame_cb));
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_OK, get_frame_cb.result());
+
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallEncode(encoder.get(), video_frame1, PP_FALSE, &encode_cb1));
+ ASSERT_FALSE(encode_cb1.called());
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallEncode(encoder.get(), video_frame2, PP_FALSE, &encode_cb2));
+ ASSERT_FALSE(encode_cb2.called());
+
+ MockCompletionCallback get_bitstream_buffer_cb;
+ PP_BitstreamBuffer bitstream_buffer;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer,
+ &get_bitstream_buffer_cb));
+
+ ResourceMessageCallParams error_params(encoder.get(), 0);
+ SendNotifyError(error_params, PP_ERROR_FAILED);
+
+ ASSERT_TRUE(encode_cb1.called());
+ ASSERT_EQ(PP_ERROR_FAILED, encode_cb1.result());
+ ASSERT_TRUE(encode_cb2.called());
+ ASSERT_EQ(PP_ERROR_FAILED, encode_cb2.result());
+ ASSERT_TRUE(get_bitstream_buffer_cb.called());
+ ASSERT_EQ(PP_ERROR_FAILED, get_bitstream_buffer_cb.result());
+ }
+}
+
+TEST_F(VideoEncoderResourceTest, Close) {
+ {
+ // Check closing the encoder aborts GetVideoFrame and
+ // GetBitstreamBuffer callbacks.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+
+ MockCompletionCallback get_frame_cb;
+ PP_Resource video_frame;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetVideoFrame(encoder.get(), &video_frame, &get_frame_cb));
+ ASSERT_FALSE(get_frame_cb.called());
+
+ MockCompletionCallback get_bitstream_buffer_cb;
+ PP_BitstreamBuffer bitstream_buffer;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer,
+ &get_bitstream_buffer_cb));
+
+ CallClose(encoder.get());
+
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_ERROR_ABORTED, get_frame_cb.result());
+ ASSERT_TRUE(get_bitstream_buffer_cb.called());
+ ASSERT_EQ(PP_ERROR_ABORTED, get_bitstream_buffer_cb.result());
+ }
+ {
+ // Check closing the encoder aborts Encode and GetBitstreamBuffer
+ // callbacks.
+ LockingResourceReleaser encoder(CreateAndInitializeEncoder());
+
+ MockCompletionCallback get_frame_cb, encode_cb1, encode_cb2;
+ PP_Resource video_frame1, video_frame2;
+ ASSERT_EQ(
+ PP_OK_COMPLETIONPENDING,
+ CallFirstGetVideoFrame(encoder.get(), &video_frame1, &get_frame_cb));
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_OK, get_frame_cb.result());
+
+ get_frame_cb.Reset();
+ ASSERT_EQ(
+ PP_OK_COMPLETIONPENDING,
+ CallFirstGetVideoFrame(encoder.get(), &video_frame2, &get_frame_cb));
+ ASSERT_TRUE(get_frame_cb.called());
+ ASSERT_EQ(PP_OK, get_frame_cb.result());
+
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallEncode(encoder.get(), video_frame1, PP_FALSE, &encode_cb1));
+ ASSERT_FALSE(encode_cb1.called());
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallEncode(encoder.get(), video_frame2, PP_FALSE, &encode_cb2));
+ ASSERT_FALSE(encode_cb2.called());
+
+ MockCompletionCallback get_bitstream_buffer_cb;
+ PP_BitstreamBuffer bitstream_buffer;
+ ASSERT_EQ(PP_OK_COMPLETIONPENDING,
+ CallGetBitstreamBuffer(encoder.get(), &bitstream_buffer,
+ &get_bitstream_buffer_cb));
+
+ CallClose(encoder.get());
+
+ ASSERT_TRUE(encode_cb1.called());
+ ASSERT_EQ(PP_ERROR_ABORTED, encode_cb1.result());
+ ASSERT_TRUE(encode_cb2.called());
+ ASSERT_EQ(PP_ERROR_ABORTED, encode_cb2.result());
+ ASSERT_TRUE(get_bitstream_buffer_cb.called());
+ ASSERT_EQ(PP_ERROR_ABORTED, get_bitstream_buffer_cb.result());
+ }
+}
+
+} // namespace proxy
+} // namespace ppapi
diff --git a/ppapi/tests/test_video_encoder.cc b/ppapi/tests/test_video_encoder.cc
new file mode 100644
index 0000000..4737ed0
--- /dev/null
+++ b/ppapi/tests/test_video_encoder.cc
@@ -0,0 +1,41 @@
+// Copyright 2015 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 "ppapi/tests/test_video_encoder.h"
+
+#include "ppapi/c/pp_codecs.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/cpp/video_encoder.h"
+#include "ppapi/tests/testing_instance.h"
+
+REGISTER_TEST_CASE(VideoEncoder);
+
+bool TestVideoEncoder::Init() {
+ video_encoder_interface_ = static_cast<const PPB_VideoEncoder_0_1*>(
+ pp::Module::Get()->GetBrowserInterface(PPB_VIDEOENCODER_INTERFACE_0_1));
+ return video_encoder_interface_ && CheckTestingInterface();
+}
+
+void TestVideoEncoder::RunTests(const std::string& filter) {
+ RUN_CALLBACK_TEST(TestVideoEncoder, Create, filter);
+}
+
+std::string TestVideoEncoder::TestCreate() {
+ // Test that we get results for supported formats.
+ {
+ pp::VideoEncoder video_encoder(instance_);
+ ASSERT_FALSE(video_encoder.is_null());
+
+ TestCompletionCallbackWithOutput<std::vector<PP_VideoProfileDescription> >
+ callback(instance_->pp_instance(), false);
+ callback.WaitForResult(
+ video_encoder.GetSupportedProfiles(callback.GetCallback()));
+
+ ASSERT_GE(callback.output().size(), 0U);
+ }
+ // TODO(llandwerlin): add more tests once software video encode is
+ // available.
+
+ PASS();
+}
diff --git a/ppapi/tests/test_video_encoder.h b/ppapi/tests/test_video_encoder.h
new file mode 100644
index 0000000..f814f9e
--- /dev/null
+++ b/ppapi/tests/test_video_encoder.h
@@ -0,0 +1,29 @@
+// Copyright 2015 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.
+
+#ifndef PPAPI_TESTS_TEST_VIDEO_ENCODER_H_
+#define PPAPI_TESTS_TEST_VIDEO_ENCODER_H_
+
+#include <string>
+
+#include "ppapi/c/pp_stdint.h"
+#include "ppapi/c/ppb_video_encoder.h"
+#include "ppapi/tests/test_case.h"
+
+class TestVideoEncoder : public TestCase {
+ public:
+ explicit TestVideoEncoder(TestingInstance* instance) : TestCase(instance) {}
+
+ private:
+ // TestCase implementation.
+ virtual bool Init();
+ virtual void RunTests(const std::string& filter);
+
+ std::string TestCreate();
+
+ // Used by the tests that access the C API directly.
+ const PPB_VideoEncoder_0_1* video_encoder_interface_;
+};
+
+#endif // PPAPI_TESTS_TEST_VIDEO_ENCODER_H_