// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "ppapi/proxy/video_encoder_resource.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/memory/unsafe_shared_memory_region.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/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_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
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_2_Thunk()),
encoder_iface_0_1_(thunk::GetPPB_VideoEncoder_0_1_Thunk()),
video_frames_manager_(this) {}
~VideoEncoderResourceTest() override {}
const PPB_VideoEncoder_0_2* encoder_iface() const { return encoder_iface_; }
const PPB_VideoEncoder_0_1* encoder_iface_0_1() const {
return encoder_iface_0_1_;
}
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,
SerializedHandle handle) {
ResourceMessageReplyParams reply_params(params.pp_resource(),
params.sequence());
reply_params.set_result(result);
reply_params.AppendHandle(std::move(handle));
PluginMessageFilter::DispatchResourceReplyForTest(reply_params,
nested_message);
}
void SendReplyWithHandles(const ResourceMessageCallParams& params,
int32_t result,
const IPC::Message& nested_message,
std::vector<SerializedHandle> handles) {
ResourceMessageReplyParams reply_params(params.pp_resource(),
params.sequence());
reply_params.set_result(result);
for (auto& handle : handles)
reply_params.AppendHandle(std::move(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) {
base::UnsafeSharedMemoryRegion region =
base::UnsafeSharedMemoryRegion::Create(buffer_size);
ASSERT_TRUE(region.IsValid());
shared_memory_bitstreams_.push_back(std::move(region));
}
}
void CreateVideoFramesSharedMemory(uint32_t frame_length,
uint32_t frame_count) {
uint32_t buffer_length =
frame_length + sizeof(ppapi::MediaStreamBuffer::Video);
base::UnsafeSharedMemoryRegion shared_memory_frames =
base::UnsafeSharedMemoryRegion::Create(buffer_length * frame_count);
ASSERT_TRUE(shared_memory_frames.IsValid());
ASSERT_TRUE(video_frames_manager_.SetBuffers(
frame_count, buffer_length, std::move(shared_memory_frames), 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, ¶ms, &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(¶ms);
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(¶ms, &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 (const auto& mem : shared_memory_bitstreams_) {
ASSERT_EQ(mem.GetSize(), buffer_length);
base::UnsafeSharedMemoryRegion region = mem.Duplicate();
ASSERT_TRUE(region.IsValid());
handles.push_back(SerializedHandle(std::move(region)));
}
SendReplyWithHandles(
params, PP_OK,
PpapiPluginMsg_VideoEncoder_BitstreamBuffers(buffer_length),
std::move(handles));
}
void SendGetVideoFramesReply(const ResourceMessageCallParams& params,
uint32_t frame_count,
uint32_t frame_length,
const PP_Size& size) {
base::UnsafeSharedMemoryRegion region =
video_frames_manager_.region().Duplicate();
ASSERT_TRUE(region.IsValid());
SendReplyWithHandle(
params, PP_OK,
PpapiPluginMsg_VideoEncoder_GetVideoFramesReply(
frame_count, frame_length + sizeof(MediaStreamBuffer::Video), size),
SerializedHandle(std::move(region)));
}
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_2* encoder_iface_;
const PPB_VideoEncoder_0_1* encoder_iface_0_1_;
std::vector<base::UnsafeSharedMemoryRegion> 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(¶ms));
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.hardware_accelerated = PP_TRUE;
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.hardware_accelerated = PP_FALSE;
profiles_response.push_back(profile);
SendGetSupportedProfilesReply(params, profiles_response);
ASSERT_EQ(profiles_response.size(), static_cast<uint32_t>(cb.result()));
ASSERT_EQ(0, memcmp(&profiles[0], &profiles_response[0], sizeof(profiles)));
}
}
TEST_F(VideoEncoderResourceTest, GetSupportedProfiles0_1) {
// Verifies that GetSupportedProfiles calls into the renderer and
// the we get the right results back.
{
LockingResourceReleaser encoder(CreateEncoder());
PP_VideoProfileDescription_0_1 profiles[2];
PP_ArrayOutput output;
output.user_data = &profiles[0];
output.GetDataBuffer = ForwardUserData;
ResourceMessageCallParams params;
MockCompletionCallback cb;
int32_t result = encoder_iface_0_1()->GetSupportedProfiles(
encoder.get(), output, PP_MakeOptionalCompletionCallback(
&MockCompletionCallback::Callback, &cb));
ASSERT_EQ(PP_OK_COMPLETIONPENDING, result);
ASSERT_TRUE(CheckGetSupportedProfilesMsg(¶ms));
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.hardware_accelerated = PP_TRUE;
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.hardware_accelerated = PP_FALSE;
profiles_response.push_back(profile);
SendGetSupportedProfilesReply(params, profiles_response);
ASSERT_EQ(profiles_response.size(), static_cast<uint32_t>(cb.result()));
for (uint32_t i = 0; i < profiles_response.size(); i++) {
ASSERT_EQ(profiles_response[i].profile, profiles[i].profile);
ASSERT_EQ(profiles_response[i].max_resolution.width,
profiles[i].max_resolution.width);
ASSERT_EQ(profiles_response[i].max_resolution.height,
profiles[i].max_resolution.height);
ASSERT_EQ(profiles_response[i].max_framerate_numerator,
profiles[i].max_framerate_numerator);
ASSERT_EQ(profiles_response[i].max_framerate_denominator,
profiles[i].max_framerate_denominator);
if (profiles_response[i].hardware_accelerated)
ASSERT_EQ(PP_HARDWAREACCELERATION_ONLY, profiles[i].acceleration);
else
ASSERT_EQ(PP_HARDWAREACCELERATION_NONE, profiles[i].acceleration);
}
}
}
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(¶ms, &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(¶ms, &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(¶ms, &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
// successful.
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(¶ms, &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(¶ms, &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(¶ms, &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(¶ms, &buffer_id));
uint32_t bitrate, framerate;
CallRequestEncodingParametersChange(encoder.get(), 0, 0);
ASSERT_FALSE(
CheckRequestEncodingParametersChangeMsg(¶ms, &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(¶ms));
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(¶ms, &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(¶ms, &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());
// Verify that a remaining encode response from the renderer is
// discarded.
ResourceMessageCallParams params;
uint32_t frame_id;
bool force_frame;
ASSERT_TRUE(CheckEncodeMsg(¶ms, &frame_id, &force_frame));
SendEncodeReply(params, frame_id);
}
}
} // namespace proxy
} // namespace ppapi