blob: e3373a8b0654a374f80af9bbc7f9d98ad76ea130 [file] [log] [blame]
// Copyright 2014 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 <algorithm>
#include "base/single_thread_task_runner.h"
#include "base/test/scoped_task_environment.h"
#include "content/renderer/media/webrtc/webrtc_video_frame_adapter.h"
#include "content/renderer/media/webrtc/webrtc_video_track_source.h"
#include "media/base/video_frame.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/api/video/video_frame.h"
#include "third_party/webrtc/rtc_base/ref_counted_object.h"
using testing::_;
using testing::Invoke;
using testing::Mock;
namespace content {
void ExpectUpdateRectEquals(const gfx::Rect& expected,
const webrtc::VideoFrame::UpdateRect actual) {
EXPECT_EQ(expected.x(), actual.offset_x);
EXPECT_EQ(expected.y(), actual.offset_y);
EXPECT_EQ(expected.width(), actual.width);
EXPECT_EQ(expected.height(), actual.height);
}
class MockVideoSink : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
public:
MOCK_METHOD1(OnFrame, void(const webrtc::VideoFrame&));
};
class WebRtcVideoTrackSourceTest : public ::testing::Test {
public:
WebRtcVideoTrackSourceTest()
: track_source_(new rtc::RefCountedObject<WebRtcVideoTrackSource>(
/*is_screencast=*/false,
/*needs_denoising=*/absl::nullopt)) {
track_source_->AddOrUpdateSink(&mock_sink_, rtc::VideoSinkWants());
}
~WebRtcVideoTrackSourceTest() override {
track_source_->RemoveSink(&mock_sink_);
}
void SendTestFrame(const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size) {
scoped_refptr<media::VideoFrame> frame = media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_I420, coded_size, visible_rect, natural_size,
base::TimeDelta());
track_source_->OnFrameCaptured(frame);
}
void SendTestFrameWithUpdateRect(const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
int capture_counter,
const gfx::Rect& update_rect) {
scoped_refptr<media::VideoFrame> frame = media::VideoFrame::CreateFrame(
media::PIXEL_FORMAT_I420, coded_size, visible_rect, natural_size,
base::TimeDelta());
frame->metadata()->SetInteger(media::VideoFrameMetadata::CAPTURE_COUNTER,
capture_counter);
frame->metadata()->SetRect(media::VideoFrameMetadata::CAPTURE_UPDATE_RECT,
update_rect);
track_source_->OnFrameCaptured(frame);
}
WebRtcVideoTrackSource::FrameAdaptationParams FrameAdaptation_KeepAsIs(
const gfx::Size& natural_size) {
return WebRtcVideoTrackSource::FrameAdaptationParams{
false /*should_drop_frame*/,
0 /*crop_x*/,
0 /*crop_y*/,
natural_size.width() /*crop_width*/,
natural_size.height() /*crop_height*/,
natural_size.width() /*scale_to_width*/,
natural_size.height() /*scale_to_height*/
};
}
WebRtcVideoTrackSource::FrameAdaptationParams FrameAdaptation_DropFrame() {
return WebRtcVideoTrackSource::FrameAdaptationParams{
true /*should_drop_frame*/,
0 /*crop_x*/,
0 /*crop_y*/,
0 /*crop_width*/,
0 /*crop_height*/,
0 /*scale_to_width*/,
0 /*scale_to_height*/
};
}
WebRtcVideoTrackSource::FrameAdaptationParams FrameAdaptation_Scale(
const gfx::Size& natural_size,
const gfx::Size& scale_to_size) {
return WebRtcVideoTrackSource::FrameAdaptationParams{
false /*should_drop_frame*/,
0 /*crop_x*/,
0 /*crop_y*/,
natural_size.width() /*crop_width*/,
natural_size.height() /*crop_height*/,
scale_to_size.width() /*scale_to_width*/,
scale_to_size.height() /*scale_to_height*/
};
}
protected:
MockVideoSink mock_sink_;
scoped_refptr<WebRtcVideoTrackSource> track_source_;
};
TEST_F(WebRtcVideoTrackSourceTest, CropFrameTo640360) {
const gfx::Size kCodedSize(640, 480);
const gfx::Rect kVisibleRect(0, 60, 640, 360);
const gfx::Size kNaturalSize(640, 360);
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_KeepAsIs(kNaturalSize));
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([kNaturalSize](const webrtc::VideoFrame& frame) {
EXPECT_EQ(kNaturalSize.width(), frame.width());
EXPECT_EQ(kNaturalSize.height(), frame.height());
}));
SendTestFrame(kCodedSize, kVisibleRect, kNaturalSize);
}
TEST_F(WebRtcVideoTrackSourceTest, CropFrameTo320320) {
const gfx::Size kCodedSize(640, 480);
const gfx::Rect kVisibleRect(80, 0, 480, 480);
const gfx::Size kNaturalSize(320, 320);
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_KeepAsIs(kNaturalSize));
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([kNaturalSize](const webrtc::VideoFrame& frame) {
EXPECT_EQ(kNaturalSize.width(), frame.width());
EXPECT_EQ(kNaturalSize.height(), frame.height());
}));
SendTestFrame(kCodedSize, kVisibleRect, kNaturalSize);
}
TEST_F(WebRtcVideoTrackSourceTest, Scale720To640360) {
const gfx::Size kCodedSize(1280, 720);
const gfx::Rect kVisibleRect(0, 0, 1280, 720);
const gfx::Size kNaturalSize(640, 360);
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_KeepAsIs(kNaturalSize));
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([kNaturalSize](const webrtc::VideoFrame& frame) {
EXPECT_EQ(kNaturalSize.width(), frame.width());
EXPECT_EQ(kNaturalSize.height(), frame.height());
}));
SendTestFrame(kCodedSize, kVisibleRect, kNaturalSize);
}
TEST_F(WebRtcVideoTrackSourceTest, UpdateRectWithNoTransform) {
const gfx::Size kCodedSize(640, 480);
const gfx::Rect kVisibleRect(0, 0, 640, 480);
const gfx::Size kNaturalSize(640, 480);
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_KeepAsIs(kNaturalSize));
// Any UPDATE_RECT for the first received frame is expected to get
// ignored and the full frame should be marked as updated.
const gfx::Rect kUpdateRect1(1, 2, 3, 4);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(gfx::Rect(0, 0, frame.width(), frame.height()),
frame.update_rect());
}));
int capture_counter = 101; // arbitrary absolute value
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
capture_counter, kUpdateRect1);
Mock::VerifyAndClearExpectations(&mock_sink_);
// Update rect for second frame should get passed along.
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([kUpdateRect1](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(kUpdateRect1, frame.update_rect());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kUpdateRect1);
Mock::VerifyAndClearExpectations(&mock_sink_);
// Simulate the next frame getting dropped
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_DropFrame());
const gfx::Rect kUpdateRect2(2, 3, 4, 5);
EXPECT_CALL(mock_sink_, OnFrame(_)).Times(0);
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kUpdateRect2);
Mock::VerifyAndClearExpectations(&mock_sink_);
// The |update_rect| for the next frame is expected to contain the union
// of the current an previous |update_rects|.
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_KeepAsIs(kNaturalSize));
const gfx::Rect kUpdateRect3(3, 4, 5, 6);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(
Invoke([kUpdateRect2, kUpdateRect3](const webrtc::VideoFrame& frame) {
gfx::Rect expected_update_rect(kUpdateRect2);
expected_update_rect.Union(kUpdateRect3);
ExpectUpdateRectEquals(expected_update_rect, frame.update_rect());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kUpdateRect3);
Mock::VerifyAndClearExpectations(&mock_sink_);
// Simulate a gap in |capture_counter|. This is expected to cause the whole
// frame to get marked as updated.
++capture_counter;
const gfx::Rect kUpdateRect4(4, 5, 6, 7);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([kVisibleRect](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(kVisibleRect, frame.update_rect());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kUpdateRect4);
Mock::VerifyAndClearExpectations(&mock_sink_);
// Important edge case (expected to be fairly common): An empty update rect
// indicates that nothing has changed.
const gfx::Rect kEmptyRectWithZeroOrigin(0, 0, 0, 0);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
EXPECT_TRUE(frame.update_rect().IsEmpty());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kEmptyRectWithZeroOrigin);
Mock::VerifyAndClearExpectations(&mock_sink_);
const gfx::Rect kEmptyRectWithNonZeroOrigin(10, 20, 0, 0);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
EXPECT_TRUE(frame.update_rect().IsEmpty());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kEmptyRectWithNonZeroOrigin);
Mock::VerifyAndClearExpectations(&mock_sink_);
// A frame without a CAPTURE_COUNTER and CAPTURE_UPDATE_RECT is treated as the
// whole content having changed.
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([kVisibleRect](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(kVisibleRect, frame.update_rect());
}));
SendTestFrame(kCodedSize, kVisibleRect, kNaturalSize);
Mock::VerifyAndClearExpectations(&mock_sink_);
}
TEST_F(WebRtcVideoTrackSourceTest, UpdateRectWithCropFromUpstream) {
const gfx::Size kCodedSize(640, 480);
const gfx::Rect kVisibleRect(100, 50, 200, 80);
const gfx::Size kNaturalSize = gfx::Size(200, 80);
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_KeepAsIs(kNaturalSize));
// Any UPDATE_RECT for the first received frame is expected to get
// ignored and the full frame should be marked as updated.
const gfx::Rect kUpdateRect1(120, 70, 160, 40);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(gfx::Rect(0, 0, frame.width(), frame.height()),
frame.update_rect());
}));
int capture_counter = 101; // arbitrary absolute value
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
capture_counter, kUpdateRect1);
Mock::VerifyAndClearExpectations(&mock_sink_);
// Update rect for second frame should get passed along.
// Update rect fully contained in crop region.
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(
Invoke([kUpdateRect1, kVisibleRect](const webrtc::VideoFrame& frame) {
gfx::Rect expected_update_rect(kUpdateRect1);
expected_update_rect.Offset(-kVisibleRect.x(), -kVisibleRect.y());
ExpectUpdateRectEquals(expected_update_rect, frame.update_rect());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kUpdateRect1);
Mock::VerifyAndClearExpectations(&mock_sink_);
// Update rect outside crop region.
const gfx::Rect kUpdateRect2(2, 3, 4, 5);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
EXPECT_TRUE(frame.update_rect().IsEmpty());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kUpdateRect2);
Mock::VerifyAndClearExpectations(&mock_sink_);
// Update rect partly overlapping crop region.
const gfx::Rect kUpdateRect3(kVisibleRect.x() + 10, kVisibleRect.y() + 8,
kVisibleRect.width(), kVisibleRect.height());
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([kVisibleRect](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(gfx::Rect(10, 8, kVisibleRect.width() - 10,
kVisibleRect.height() - 8),
frame.update_rect());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kUpdateRect3);
Mock::VerifyAndClearExpectations(&mock_sink_);
// When crop origin changes, the whole frame is expected to be marked as
// changed.
const gfx::Rect kVisibleRect2(kVisibleRect.x() + 1, kVisibleRect.y(),
kVisibleRect.width(), kVisibleRect.height());
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(gfx::Rect(0, 0, frame.width(), frame.height()),
frame.update_rect());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect2, kNaturalSize,
++capture_counter, kUpdateRect1);
Mock::VerifyAndClearExpectations(&mock_sink_);
// When crop size changes, the whole frame is expected to be marked as
// changed.
const gfx::Rect kVisibleRect3(kVisibleRect2.x(), kVisibleRect2.y(),
kVisibleRect2.width(),
kVisibleRect2.height() - 1);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(gfx::Rect(0, 0, frame.width(), frame.height()),
frame.update_rect());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect3, kNaturalSize,
++capture_counter, kUpdateRect1);
Mock::VerifyAndClearExpectations(&mock_sink_);
}
TEST_F(WebRtcVideoTrackSourceTest, UpdateRectWithScaling) {
const gfx::Size kCodedSize(640, 480);
const gfx::Rect kVisibleRect(100, 50, 200, 80);
const gfx::Size kNaturalSize = gfx::Size(200, 80);
const gfx::Size kScaleToSize = gfx::Size(120, 50);
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_Scale(kNaturalSize, kScaleToSize));
// Any UPDATE_RECT for the first received frame is expected to get
// ignored and the full frame should be marked as updated.
const gfx::Rect kUpdateRect1(120, 70, 160, 40);
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(gfx::Rect(0, 0, frame.width(), frame.height()),
frame.update_rect());
}));
int capture_counter = 101; // arbitrary absolute value
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
capture_counter, kUpdateRect1);
Mock::VerifyAndClearExpectations(&mock_sink_);
// When scaling is applied and UPDATE_RECT is not empty, we always expect a
// full update rect.
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(gfx::Rect(0, 0, frame.width(), frame.height()),
frame.update_rect());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, kUpdateRect1);
// When UPDATE_RECT is empty, we expect to deliver an empty UpdateRect even if
// scaling is applied.
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
EXPECT_TRUE(frame.update_rect().IsEmpty());
}));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, gfx::Rect());
// When UPDATE_RECT is empty, but the scaling has changed, we expect to
// deliver an full UpdateRect.
EXPECT_CALL(mock_sink_, OnFrame(_))
.WillOnce(Invoke([](const webrtc::VideoFrame& frame) {
ExpectUpdateRectEquals(gfx::Rect(0, 0, frame.width(), frame.height()),
frame.update_rect());
}));
const gfx::Size kScaleToSize2 = gfx::Size(60, 26);
track_source_->SetCustomFrameAdaptationParamsForTesting(
FrameAdaptation_Scale(kNaturalSize, kScaleToSize2));
SendTestFrameWithUpdateRect(kCodedSize, kVisibleRect, kNaturalSize,
++capture_counter, gfx::Rect());
Mock::VerifyAndClearExpectations(&mock_sink_);
}
} // namespace content