blob: ddc064ea5c502a67b26704b769049e8d13109901 [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/logging.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.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/blink/renderer/platform/peerconnection/rtc_video_encoder.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"
#include "third_party/webrtc/rtc_base/time_utils.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::Values;
using ::testing::WithArgs;
namespace blink {
namespace {
const int kInputFrameFillY = 12;
const int kInputFrameFillU = 23;
const int kInputFrameFillV = 34;
const uint16_t kInputFrameHeight = 234;
const uint16_t kInputFrameWidth = 345;
const uint16_t kStartBitrate = 100;
const webrtc::VideoEncoder::Capabilities kVideoEncoderCapabilities(
/* loss_notification= */ false);
const webrtc::VideoEncoder::Settings
kVideoEncoderSettings(kVideoEncoderCapabilities, 1, 12345);
class EncodedImageCallbackWrapper : public webrtc::EncodedImageCallback {
public:
using EncodedCallback = base::OnceCallback<void(
const webrtc::EncodedImage& encoded_image,
const webrtc::CodecSpecificInfo* codec_specific_info)>;
EncodedImageCallbackWrapper(EncodedCallback encoded_callback)
: encoded_callback_(std::move(encoded_callback)) {}
Result OnEncodedImage(
const webrtc::EncodedImage& encoded_image,
const webrtc::CodecSpecificInfo* codec_specific_info) override {
std::move(encoded_callback_).Run(encoded_image, codec_specific_info);
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;
case webrtc::kVideoCodecVP9:
media_profile = media::VP9PROFILE_PROFILE0;
break;
default:
ADD_FAILURE() << "Unexpected codec type: " << codec_type;
media_profile = media::VIDEO_CODEC_PROFILE_UNKNOWN;
}
rtc_encoder_ = std::make_unique<RTCVideoEncoder>(media_profile, false,
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(
EncodedImageCallbackWrapper::EncodedCallback callback) {
callback_wrapper_ =
std::make_unique<EncodedImageCallbackWrapper>(std::move(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;
}
webrtc::VideoCodec GetDefaultTemporalLayerCodec() {
const webrtc::VideoCodecType codec_type = webrtc::kVideoCodecVP9;
webrtc::VideoCodec codec{};
codec.codecType = codec_type;
codec.width = kInputFrameWidth;
codec.height = kInputFrameHeight;
codec.startBitrate = kStartBitrate;
codec.maxBitrate = codec.startBitrate * 2;
codec.minBitrate = codec.startBitrate / 2;
codec.maxFramerate = 24;
codec.active = true;
codec.qpMax = 30;
codec.numberOfSimulcastStreams = 1;
codec.mode = webrtc::VideoCodecMode::kRealtimeVideo;
webrtc::VideoCodecVP9& vp9 = *codec.VP9();
vp9.numberOfTemporalLayers = 3;
vp9.numberOfSpatialLayers = 1;
webrtc::SpatialLayer& sl = codec.spatialLayers[0];
sl.width = kInputFrameWidth;
sl.height = kInputFrameHeight;
sl.maxFramerate = 24;
sl.numberOfTemporalLayers = vp9.numberOfTemporalLayers;
sl.targetBitrate = kStartBitrate;
sl.maxBitrate = sl.targetBitrate;
sl.minBitrate = sl.targetBitrate;
sl.qpMax = 30;
sl.active = true;
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(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(scoped_refptr<media::VideoFrame> frame,
bool force_keyframe) {
client_->BitstreamBufferReady(
0,
media::BitstreamBufferMetadata(0, force_keyframe, frame->timestamp()));
}
void ReturnTemporalLayerFrameWithVp9Metadata(
scoped_refptr<media::VideoFrame> frame,
bool force_keyframe) {
int32_t bitstream_buffer_id = frame->timestamp().InMicroseconds();
CHECK(0 <= bitstream_buffer_id && bitstream_buffer_id <= 4);
media::BitstreamBufferMetadata metadata(100u /* payload_size_bytes */,
force_keyframe, frame->timestamp());
// Assume the number of TLs is three. TL structure is below.
// TL2: [#1] /-[#3]
// TL1: /_____[#2] /
// TL0: [#0]-----------------[#4]
media::Vp9Metadata vp9;
vp9.has_reference = bitstream_buffer_id != 0 && !force_keyframe;
vp9.temporal_up_switch = bitstream_buffer_id != 3;
switch (bitstream_buffer_id) {
case 0:
vp9.temporal_idx = 0;
break;
case 1:
vp9.temporal_idx = 2;
vp9.p_diffs = {1};
break;
case 2:
vp9.temporal_idx = 1;
vp9.p_diffs = {2};
break;
case 3:
vp9.temporal_idx = 2;
vp9.p_diffs = {1, 3};
break;
case 4:
vp9.temporal_idx = 0;
vp9.p_diffs = {4};
break;
}
metadata.vp9 = vp9;
client_->BitstreamBufferReady(bitstream_buffer_id, metadata);
}
void VerifyTimestamp(uint32_t rtp_timestamp,
int64_t capture_time_ms,
const webrtc::EncodedImage& encoded_image,
const webrtc::CodecSpecificInfo* codec_specific_info) {
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, kVideoEncoderSettings));
}
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, kVideoEncoderSettings));
ExpectCreateInitAndDestroyVEA();
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->InitEncode(&codec, kVideoEncoderSettings));
}
INSTANTIATE_TEST_SUITE_P(CodecProfiles,
RTCVideoEncoderTest,
Values(webrtc::kVideoCodecVP8,
webrtc::kVideoCodecH264));
TEST_F(RTCVideoEncoderTest, CreateAndInitSucceedsForTemporalLayer) {
webrtc::VideoCodec tl_codec = GetDefaultTemporalLayerCodec();
CreateEncoder(tl_codec.codecType);
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->InitEncode(&tl_codec, kVideoEncoderSettings));
}
// 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, kVideoEncoderSettings));
EXPECT_CALL(*mock_vea_, Encode(_, _))
.WillOnce(Invoke([this](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::VideoFrameType> frame_types;
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->Encode(webrtc::VideoFrame::Builder()
.set_video_frame_buffer(buffer)
.set_timestamp_rtp(0)
.set_timestamp_us(0)
.set_rotation(webrtc::kVideoRotation_0)
.build(),
&frame_types));
RunUntilIdle();
// Expect the next frame to return SW fallback.
EXPECT_EQ(WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE,
rtc_encoder_->Encode(webrtc::VideoFrame::Builder()
.set_video_frame_buffer(buffer)
.set_timestamp_rtp(0)
.set_timestamp_us(0)
.set_rotation(webrtc::kVideoRotation_0)
.build(),
&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, kVideoEncoderSettings));
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::VideoFrameType> frame_types;
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->Encode(webrtc::VideoFrame::Builder()
.set_video_frame_buffer(buffer)
.set_timestamp_rtp(0)
.set_timestamp_us(0)
.set_rotation(webrtc::kVideoRotation_0)
.build(),
&frame_types));
const rtc::scoped_refptr<webrtc::I420Buffer> upscaled_buffer =
webrtc::I420Buffer::Create(2 * kInputFrameWidth, 2 * kInputFrameHeight);
FillFrameBuffer(upscaled_buffer);
webrtc::VideoFrame rtc_frame = webrtc::VideoFrame::Builder()
.set_video_frame_buffer(upscaled_buffer)
.set_timestamp_rtp(0)
.set_timestamp_us(0)
.set_rotation(webrtc::kVideoRotation_0)
.build();
rtc_frame.set_ntp_time_ms(123456);
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->Encode(rtc_frame, &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, kVideoEncoderSettings));
const uint32_t rtp_timestamp = 1234567;
const uint32_t capture_time_ms = 3456789;
RegisterEncodeCompleteCallback(
base::BindOnce(&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::VideoFrameType> frame_types;
webrtc::VideoFrame rtc_frame = webrtc::VideoFrame::Builder()
.set_video_frame_buffer(buffer)
.set_timestamp_rtp(rtp_timestamp)
.set_timestamp_us(0)
.set_rotation(webrtc::kVideoRotation_0)
.build();
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, &frame_types));
}
TEST_F(RTCVideoEncoderTest, EncodeTemporalLayer) {
webrtc::VideoCodec tl_codec = GetDefaultTemporalLayerCodec();
CreateEncoder(tl_codec.codecType);
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->InitEncode(&tl_codec, kVideoEncoderSettings));
size_t kNumEncodeFrames = 5u;
EXPECT_CALL(*mock_vea_, Encode(_, _))
.Times(kNumEncodeFrames)
.WillRepeatedly(Invoke(
this, &RTCVideoEncoderTest::ReturnTemporalLayerFrameWithVp9Metadata));
for (size_t i = 0; i < kNumEncodeFrames; i++) {
const rtc::scoped_refptr<webrtc::I420Buffer> buffer =
webrtc::I420Buffer::Create(kInputFrameWidth, kInputFrameHeight);
FillFrameBuffer(buffer);
std::vector<webrtc::VideoFrameType> frame_types;
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
rtc_encoder_->Encode(webrtc::VideoFrame::Builder()
.set_video_frame_buffer(buffer)
.set_timestamp_rtp(0)
.set_timestamp_us(i)
.set_rotation(webrtc::kVideoRotation_0)
.build(),
&frame_types));
}
}
} // namespace blink