blob: ee2ad1d0f1b360a0c56c4b4421d9d22e500cb5dd [file] [log] [blame]
// Copyright 2016 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 <stdint.h>
#include "base/bind.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "content/renderer/media/webrtc/rtc_video_encoder.h"
#include "media/video/mock_gpu_video_accelerator_factories.h"
#include "media/video/mock_video_encode_accelerator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libyuv/include/libyuv/planar_functions.h"
#include "third_party/webrtc/api/video/i420_buffer.h"
#include "third_party/webrtc/api/video_codecs/video_encoder.h"
#include "third_party/webrtc/modules/video_coding/include/video_codec_interface.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::Values;
using ::testing::WithArgs;
namespace content {
namespace {
const int kInputFrameFillY = 12;
const int kInputFrameFillU = 23;
const int kInputFrameFillV = 34;
const unsigned short kInputFrameHeight = 234;
const unsigned short kInputFrameWidth = 345;
const unsigned short kStartBitrate = 100;
class EncodedImageCallbackWrapper : public webrtc::EncodedImageCallback {
public:
using EncodedCallback =
base::Callback<void(const webrtc::EncodedImage& encoded_image,
const webrtc::CodecSpecificInfo* codec_specific_info,
const webrtc::RTPFragmentationHeader* fragmentation)>;
EncodedImageCallbackWrapper(const EncodedCallback& encoded_callback)
: encoded_callback_(encoded_callback) {}
Result OnEncodedImage(
const webrtc::EncodedImage& encoded_image,
const webrtc::CodecSpecificInfo* codec_specific_info,
const webrtc::RTPFragmentationHeader* fragmentation) override {
encoded_callback_.Run(encoded_image, codec_specific_info, fragmentation);
return Result(Result::OK);
};
private:
EncodedCallback encoded_callback_;
};
} // anonymous namespace
class RTCVideoEncoderTest
: public ::testing::TestWithParam<webrtc::VideoCodecType> {
public:
RTCVideoEncoderTest()
: encoder_thread_("vea_thread"),
mock_gpu_factories_(
new media::MockGpuVideoAcceleratorFactories(nullptr)),
idle_waiter_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
media::MockVideoEncodeAccelerator* ExpectCreateInitAndDestroyVEA() {
// The VEA will be owned by the RTCVideoEncoder once
// factory.CreateVideoEncodeAccelerator() is called.
media::MockVideoEncodeAccelerator* mock_vea =
new media::MockVideoEncodeAccelerator();
EXPECT_CALL(*mock_gpu_factories_.get(), DoCreateVideoEncodeAccelerator())
.WillRepeatedly(Return(mock_vea));
EXPECT_CALL(*mock_vea, Initialize(_, _))
.WillOnce(Invoke(this, &RTCVideoEncoderTest::Initialize));
EXPECT_CALL(*mock_vea, UseOutputBitstreamBuffer(_)).Times(AtLeast(3));
EXPECT_CALL(*mock_vea, Destroy()).Times(1);
return mock_vea;
}
void SetUp() override {
DVLOG(3) << __func__;
ASSERT_TRUE(encoder_thread_.Start());
EXPECT_CALL(*mock_gpu_factories_.get(), GetTaskRunner())
.WillRepeatedly(Return(encoder_thread_.task_runner()));
mock_vea_ = ExpectCreateInitAndDestroyVEA();
}
void TearDown() override {
DVLOG(3) << __func__;
EXPECT_TRUE(encoder_thread_.IsRunning());
RunUntilIdle();
rtc_encoder_->Release();
RunUntilIdle();
encoder_thread_.Stop();
}
void RunUntilIdle() {
DVLOG(3) << __func__;
encoder_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal,
base::Unretained(&idle_waiter_)));
idle_waiter_.Wait();
}
void CreateEncoder(webrtc::VideoCodecType codec_type) {
DVLOG(3) << __func__;
media::VideoCodecProfile media_profile;
switch (codec_type) {
case webrtc::kVideoCodecVP8:
media_profile = media::VP8PROFILE_ANY;
break;
case webrtc::kVideoCodecH264:
media_profile = media::H264PROFILE_BASELINE;
break;
default:
ADD_FAILURE() << "Unexpected codec type: " << codec_type;
media_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN;
}
rtc_encoder_ = std::make_unique<RTCVideoEncoder>(media_profile,
mock_gpu_factories_.get());
}
// media::VideoEncodeAccelerator implementation.
bool Initialize(const media::VideoEncodeAccelerator::Config& config,
media::VideoEncodeAccelerator::Client* client) {
DVLOG(3) << __func__;
client_ = client;
client_->RequireBitstreamBuffers(0, config.input_visible_size,
config.input_visible_size.GetArea());
return true;
}
void RegisterEncodeCompleteCallback(
const EncodedImageCallbackWrapper::EncodedCallback& callback) {
callback_wrapper_.reset(new EncodedImageCallbackWrapper(callback));
rtc_encoder_->RegisterEncodeCompleteCallback(callback_wrapper_.get());
}
webrtc::VideoCodec GetDefaultCodec() {
webrtc::VideoCodec codec = {};
memset(&codec, 0, sizeof(codec));
codec.width = kInputFrameWidth;
codec.height = kInputFrameHeight;
codec.codecType = webrtc::kVideoCodecVP8;
codec.startBitrate = kStartBitrate;
return codec;
}
void FillFrameBuffer(rtc::scoped_refptr<webrtc::I420Buffer> frame) {
CHECK(libyuv::I420Rect(frame->MutableDataY(), frame->StrideY(),
frame->MutableDataU(), frame->StrideU(),
frame->MutableDataV(), frame->StrideV(), 0, 0,
frame->width(), frame->height(), kInputFrameFillY,
kInputFrameFillU, kInputFrameFillV) == 0);
}
void VerifyEncodedFrame(const scoped_refptr<media::VideoFrame>& frame,
bool force_keyframe) {
DVLOG(3) << __func__;
EXPECT_EQ(kInputFrameWidth, frame->visible_rect().width());
EXPECT_EQ(kInputFrameHeight, frame->visible_rect().height());
EXPECT_EQ(kInputFrameFillY,
frame->visible_data(media::VideoFrame::kYPlane)[0]);
EXPECT_EQ(kInputFrameFillU,
frame->visible_data(media::VideoFrame::kUPlane)[0]);
EXPECT_EQ(kInputFrameFillV,
frame->visible_data(media::VideoFrame::kVPlane)[0]);
}
void ReturnFrameWithTimeStamp(const scoped_refptr<media::VideoFrame>& frame,
bool force_keyframe) {
client_->BitstreamBufferReady(
0,
media::BitstreamBufferMetadata(0, force_keyframe, frame->timestamp()));
}
void VerifyTimestamp(uint32_t rtp_timestamp,
int64_t capture_time_ms,
const webrtc::EncodedImage& encoded_image,
const webrtc::CodecSpecificInfo* codec_specific_info,
const webrtc::RTPFragmentationHeader* fragmentation) {
DVLOG(3) << __func__;
EXPECT_EQ(rtp_timestamp, encoded_image.Timestamp());
EXPECT_EQ(capture_time_ms, encoded_image.capture_time_ms_);
}
protected:
media::MockVideoEncodeAccelerator* mock_vea_;
std::unique_ptr<RTCVideoEncoder> rtc_encoder_;
media::VideoEncodeAccelerator::Client* client_;
base::Thread encoder_thread_;
private:
std::unique_ptr<media::MockGpuVideoAcceleratorFactories> mock_gpu_factories_;
std::unique_ptr<EncodedImageCallbackWrapper> callback_wrapper_;
base::WaitableEvent idle_waiter_;
};
TEST_P(RTCVideoEncoderTest, CreateAndInitSucceeds) {
const webrtc::VideoCodecType codec_type = GetParam();
CreateEncoder(codec_type);
webrtc::VideoCodec codec = GetDefaultCodec();
codec.codecType = codec_type;
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
}
TEST_P(RTCVideoEncoderTest, RepeatedInitSucceeds) {
const webrtc::VideoCodecType codec_type = GetParam();
CreateEncoder(codec_type);
webrtc::VideoCodec codec = GetDefaultCodec();
codec.codecType = codec_type;
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
ExpectCreateInitAndDestroyVEA();
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
}
INSTANTIATE_TEST_CASE_P(CodecProfiles,
RTCVideoEncoderTest,
Values(webrtc::kVideoCodecVP8,
webrtc::kVideoCodecH264));
// Checks that WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE is returned when there is
// platform error.
TEST_F(RTCVideoEncoderTest, SoftwareFallbackAfterError) {
const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP8;
CreateEncoder(codec_type);
webrtc::VideoCodec codec = GetDefaultCodec();
codec.codecType = codec_type;
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
EXPECT_CALL(*mock_vea_, Encode(_, _))
.WillOnce(Invoke([this](const scoped_refptr<media::VideoFrame>&, bool) {
encoder_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
&media::VideoEncodeAccelerator::Client::NotifyError,
base::Unretained(client_),
media::VideoEncodeAccelerator::kPlatformFailureError));
}));
const rtc::scoped_refptr<webrtc::I420Buffer> buffer =
webrtc::I420Buffer::Create(kInputFrameWidth, kInputFrameHeight);
FillFrameBuffer(buffer);
std::vector<webrtc::FrameType> frame_types;
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->Encode(
webrtc::VideoFrame(buffer, 0, 0, webrtc::kVideoRotation_0),
nullptr, &frame_types));
RunUntilIdle();
// Expect the next frame to return SW fallback.
EXPECT_EQ(WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE,
rtc_encoder_->Encode(
webrtc::VideoFrame(buffer, 0, 0, webrtc::kVideoRotation_0),
nullptr, &frame_types));
}
TEST_F(RTCVideoEncoderTest, EncodeScaledFrame) {
const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP8;
CreateEncoder(codec_type);
webrtc::VideoCodec codec = GetDefaultCodec();
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
EXPECT_CALL(*mock_vea_, Encode(_, _))
.Times(2)
.WillRepeatedly(Invoke(this, &RTCVideoEncoderTest::VerifyEncodedFrame));
const rtc::scoped_refptr<webrtc::I420Buffer> buffer =
webrtc::I420Buffer::Create(kInputFrameWidth, kInputFrameHeight);
FillFrameBuffer(buffer);
std::vector<webrtc::FrameType> frame_types;
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->Encode(
webrtc::VideoFrame(buffer, 0, 0, webrtc::kVideoRotation_0),
nullptr, &frame_types));
const rtc::scoped_refptr<webrtc::I420Buffer> upscaled_buffer =
webrtc::I420Buffer::Create(2 * kInputFrameWidth, 2 * kInputFrameHeight);
FillFrameBuffer(upscaled_buffer);
webrtc::VideoFrame rtc_frame(upscaled_buffer, 0, 0, webrtc::kVideoRotation_0);
rtc_frame.set_ntp_time_ms(123456);
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->Encode(rtc_frame, nullptr, &frame_types));
}
TEST_F(RTCVideoEncoderTest, PreserveTimestamps) {
const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP8;
CreateEncoder(codec_type);
webrtc::VideoCodec codec = GetDefaultCodec();
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, rtc_encoder_->InitEncode(&codec, 1, 12345));
const uint32_t rtp_timestamp = 1234567;
const uint32_t capture_time_ms = 3456789;
RegisterEncodeCompleteCallback(
base::Bind(&RTCVideoEncoderTest::VerifyTimestamp, base::Unretained(this),
rtp_timestamp, capture_time_ms));
EXPECT_CALL(*mock_vea_, Encode(_, _))
.WillOnce(Invoke(this, &RTCVideoEncoderTest::ReturnFrameWithTimeStamp));
const rtc::scoped_refptr<webrtc::I420Buffer> buffer =
webrtc::I420Buffer::Create(kInputFrameWidth, kInputFrameHeight);
FillFrameBuffer(buffer);
std::vector<webrtc::FrameType> frame_types;
webrtc::VideoFrame rtc_frame(buffer, rtp_timestamp, 0,
webrtc::kVideoRotation_0);
rtc_frame.set_timestamp_us(capture_time_ms * rtc::kNumMicrosecsPerMillisec);
// We need to set ntp_time_ms because it will be used to derive
// media::VideoFrame timestamp.
rtc_frame.set_ntp_time_ms(4567891);
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->Encode(rtc_frame, nullptr, &frame_types));
}
} // namespace content