blob: ebbf759e4beadf076a3c6fed30c7835e7c4a3076 [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 "media/blink/video_frame_compositor.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/video_frame.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_video_frame_submitter.h"
using testing::_;
using testing::AnyNumber;
using testing::DoAll;
using testing::Eq;
using testing::Return;
using testing::StrictMock;
namespace media {
class MockWebVideoFrameSubmitter : public blink::WebVideoFrameSubmitter {
public:
// blink::WebVideoFrameSubmitter implementation.
void StopUsingProvider() override {}
MOCK_METHOD2(EnableSubmission, void(viz::SurfaceId, base::TimeTicks));
MOCK_METHOD0(StartRendering, void());
MOCK_METHOD0(StopRendering, void());
MOCK_CONST_METHOD0(IsDrivingFrameUpdates, bool(void));
MOCK_METHOD1(Initialize, void(cc::VideoFrameProvider*));
MOCK_METHOD1(SetRotation, void(media::VideoRotation));
MOCK_METHOD1(SetIsSurfaceVisible, void(bool));
MOCK_METHOD1(SetIsPageVisible, void(bool));
MOCK_METHOD1(SetForceSubmit, void(bool));
void DidReceiveFrame() override { ++did_receive_frame_count_; }
int did_receive_frame_count() { return did_receive_frame_count_; }
private:
int did_receive_frame_count_ = 0;
};
class VideoFrameCompositorTest : public VideoRendererSink::RenderCallback,
public ::testing::TestWithParam<bool> {
public:
VideoFrameCompositorTest()
: client_(new StrictMock<MockWebVideoFrameSubmitter>()) {}
void SetUp() override {
if (IsSurfaceLayerForVideoEnabled()) {
feature_list_.InitFromCommandLine("UseSurfaceLayerForVideo", "");
// When SurfaceLayerForVideo is enabled, |compositor_| owns the
// |submitter_|. Otherwise, the |compositor_| treats the |submitter_| as
// if it were a VideoFrameProviderClient in the VideoLayer code path,
// holding only a bare pointer.
}
submitter_ = client_.get();
if (!IsSurfaceLayerForVideoEnabled()) {
compositor_ = std::make_unique<VideoFrameCompositor>(
base::ThreadTaskRunnerHandle::Get(), nullptr);
compositor_->SetVideoFrameProviderClient(client_.get());
} else {
EXPECT_CALL(*submitter_, Initialize(_));
compositor_ = std::make_unique<VideoFrameCompositor>(
base::ThreadTaskRunnerHandle::Get(), std::move(client_));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*submitter_,
SetRotation(Eq(media::VideoRotation::VIDEO_ROTATION_90)));
EXPECT_CALL(*submitter_, SetForceSubmit(false));
EXPECT_CALL(*submitter_, EnableSubmission(Eq(viz::SurfaceId()), _));
compositor_->EnableSubmission(viz::SurfaceId(), base::TimeTicks(),
media::VideoRotation::VIDEO_ROTATION_90,
false);
}
compositor_->set_tick_clock_for_testing(&tick_clock_);
// Disable background rendering by default.
compositor_->set_background_rendering_for_testing(false);
}
~VideoFrameCompositorTest() override {
compositor_->SetVideoFrameProviderClient(nullptr);
}
scoped_refptr<VideoFrame> CreateOpaqueFrame() {
gfx::Size size(8, 8);
return VideoFrame::CreateFrame(PIXEL_FORMAT_I420, size, gfx::Rect(size),
size, base::TimeDelta());
}
VideoFrameCompositor* compositor() { return compositor_.get(); }
protected:
bool IsSurfaceLayerForVideoEnabled() { return GetParam(); }
// VideoRendererSink::RenderCallback implementation.
MOCK_METHOD3(Render,
scoped_refptr<VideoFrame>(base::TimeTicks,
base::TimeTicks,
bool));
MOCK_METHOD0(OnFrameDropped, void());
void StartVideoRendererSink() {
EXPECT_CALL(*submitter_, StartRendering());
const bool had_current_frame = !!compositor_->GetCurrentFrame();
compositor()->Start(this);
// If we previously had a frame, we should still have one now.
EXPECT_EQ(had_current_frame, !!compositor_->GetCurrentFrame());
base::RunLoop().RunUntilIdle();
}
void StopVideoRendererSink(bool have_client) {
if (have_client)
EXPECT_CALL(*submitter_, StopRendering());
const bool had_current_frame = !!compositor_->GetCurrentFrame();
compositor()->Stop();
// If we previously had a frame, we should still have one now.
EXPECT_EQ(had_current_frame, !!compositor_->GetCurrentFrame());
base::RunLoop().RunUntilIdle();
}
void RenderFrame() {
compositor()->GetCurrentFrame();
compositor()->PutCurrentFrame();
}
base::SimpleTestTickClock tick_clock_;
StrictMock<MockWebVideoFrameSubmitter>* submitter_;
std::unique_ptr<StrictMock<MockWebVideoFrameSubmitter>> client_;
std::unique_ptr<VideoFrameCompositor> compositor_;
private:
base::test::ScopedFeatureList feature_list_;
DISALLOW_COPY_AND_ASSIGN(VideoFrameCompositorTest);
};
TEST_P(VideoFrameCompositorTest, InitialValues) {
EXPECT_FALSE(compositor()->GetCurrentFrame().get());
}
TEST_P(VideoFrameCompositorTest, SetIsSurfaceVisible) {
if (!IsSurfaceLayerForVideoEnabled())
return;
auto cb = compositor()->GetUpdateSubmissionStateCallback();
EXPECT_CALL(*submitter_, SetIsSurfaceVisible(true));
cb.Run(true);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(*submitter_, SetIsSurfaceVisible(false));
cb.Run(false);
base::RunLoop().RunUntilIdle();
}
TEST_P(VideoFrameCompositorTest, SetIsPageVisible) {
if (!IsSurfaceLayerForVideoEnabled())
return;
EXPECT_CALL(*submitter_, SetIsPageVisible(true));
compositor()->SetIsPageVisible(true);
EXPECT_CALL(*submitter_, SetIsPageVisible(false));
compositor()->SetIsPageVisible(false);
}
TEST_P(VideoFrameCompositorTest, PaintSingleFrame) {
scoped_refptr<VideoFrame> expected = VideoFrame::CreateEOSFrame();
// Should notify compositor synchronously.
EXPECT_EQ(0, submitter_->did_receive_frame_count());
compositor()->PaintSingleFrame(expected);
scoped_refptr<VideoFrame> actual = compositor()->GetCurrentFrame();
EXPECT_EQ(expected, actual);
EXPECT_EQ(1, submitter_->did_receive_frame_count());
}
TEST_P(VideoFrameCompositorTest, VideoRendererSinkFrameDropped) {
scoped_refptr<VideoFrame> opaque_frame = CreateOpaqueFrame();
EXPECT_CALL(*this, Render(_, _, _)).WillRepeatedly(Return(opaque_frame));
StartVideoRendererSink();
EXPECT_TRUE(
compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks()));
// Another call should trigger a dropped frame callback.
EXPECT_CALL(*this, OnFrameDropped());
EXPECT_FALSE(
compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks()));
// Ensure it always happens until the frame is rendered.
EXPECT_CALL(*this, OnFrameDropped());
EXPECT_FALSE(
compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks()));
// Call GetCurrentFrame() but not PutCurrentFrame()
compositor()->GetCurrentFrame();
// The frame should still register as dropped until PutCurrentFrame is called.
EXPECT_CALL(*this, OnFrameDropped());
EXPECT_FALSE(
compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks()));
RenderFrame();
EXPECT_FALSE(
compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks()));
StopVideoRendererSink(true);
}
TEST_P(VideoFrameCompositorTest, VideoLayerShutdownWhileRendering) {
if (!IsSurfaceLayerForVideoEnabled()) {
EXPECT_CALL(*this, Render(_, _, true)).WillOnce(Return(nullptr));
StartVideoRendererSink();
compositor_->SetVideoFrameProviderClient(nullptr);
StopVideoRendererSink(false);
}
}
TEST_P(VideoFrameCompositorTest, StartFiresBackgroundRender) {
scoped_refptr<VideoFrame> opaque_frame = CreateOpaqueFrame();
EXPECT_CALL(*this, Render(_, _, true)).WillRepeatedly(Return(opaque_frame));
StartVideoRendererSink();
StopVideoRendererSink(true);
}
TEST_P(VideoFrameCompositorTest, BackgroundRenderTicks) {
scoped_refptr<VideoFrame> opaque_frame = CreateOpaqueFrame();
compositor_->set_background_rendering_for_testing(true);
base::RunLoop run_loop;
EXPECT_CALL(*this, Render(_, _, true))
.WillOnce(Return(opaque_frame))
.WillOnce(
DoAll(RunClosure(run_loop.QuitClosure()), Return(opaque_frame)));
StartVideoRendererSink();
run_loop.Run();
// UpdateCurrentFrame() calls should indicate they are not synthetic.
EXPECT_CALL(*this, Render(_, _, false)).WillOnce(Return(opaque_frame));
EXPECT_FALSE(
compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks()));
// Background rendering should tick another render callback.
StopVideoRendererSink(true);
}
TEST_P(VideoFrameCompositorTest,
UpdateCurrentFrameWorksWhenBackgroundRendered) {
scoped_refptr<VideoFrame> opaque_frame = CreateOpaqueFrame();
compositor_->set_background_rendering_for_testing(true);
// Background render a frame that succeeds immediately.
EXPECT_CALL(*this, Render(_, _, true)).WillOnce(Return(opaque_frame));
StartVideoRendererSink();
// The background render completes immediately, so the next call to
// UpdateCurrentFrame is expected to return true to account for the frame
// rendered in the background.
EXPECT_CALL(*this, Render(_, _, false))
.WillOnce(Return(scoped_refptr<VideoFrame>(opaque_frame)));
EXPECT_TRUE(
compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks()));
RenderFrame();
// Second call to UpdateCurrentFrame will return false as no new frame has
// been created since the last call.
EXPECT_CALL(*this, Render(_, _, false))
.WillOnce(Return(scoped_refptr<VideoFrame>(opaque_frame)));
EXPECT_FALSE(
compositor()->UpdateCurrentFrame(base::TimeTicks(), base::TimeTicks()));
StopVideoRendererSink(true);
}
TEST_P(VideoFrameCompositorTest, UpdateCurrentFrameIfStale) {
scoped_refptr<VideoFrame> opaque_frame_1 = CreateOpaqueFrame();
scoped_refptr<VideoFrame> opaque_frame_2 = CreateOpaqueFrame();
compositor_->set_background_rendering_for_testing(true);
EXPECT_CALL(*submitter_, IsDrivingFrameUpdates)
.Times(AnyNumber())
.WillRepeatedly(Return(true));
// Starting the video renderer should return a single frame.
EXPECT_CALL(*this, Render(_, _, true)).WillOnce(Return(opaque_frame_1));
StartVideoRendererSink();
EXPECT_EQ(opaque_frame_1, compositor()->GetCurrentFrame());
// Since we have a client, this call should not call background render, even
// if a lot of time has elapsed between calls.
tick_clock_.Advance(base::TimeDelta::FromSeconds(1));
EXPECT_CALL(*this, Render(_, _, _)).Times(0);
compositor()->UpdateCurrentFrameIfStale();
// Have the client signal that it will not drive the frame clock, so that
// calling UpdateCurrentFrameIfStale may update the frame.
EXPECT_CALL(*submitter_, IsDrivingFrameUpdates)
.Times(AnyNumber())
.WillRepeatedly(Return(false));
// Wait for background rendering to tick.
base::RunLoop run_loop;
EXPECT_CALL(*this, Render(_, _, true))
.WillOnce(
DoAll(RunClosure(run_loop.QuitClosure()), Return(opaque_frame_2)));
run_loop.Run();
// This call should still not call background render, because not enough time
// has elapsed since the last background render call.
EXPECT_CALL(*this, Render(_, _, true)).Times(0);
compositor()->UpdateCurrentFrameIfStale();
EXPECT_EQ(opaque_frame_2, compositor()->GetCurrentFrame());
// Advancing the tick clock should allow a new frame to be requested.
tick_clock_.Advance(base::TimeDelta::FromMilliseconds(10));
EXPECT_CALL(*this, Render(_, _, true)).WillOnce(Return(opaque_frame_1));
compositor()->UpdateCurrentFrameIfStale();
EXPECT_EQ(opaque_frame_1, compositor()->GetCurrentFrame());
// Clear our client, which means no mock function calls for Client. It will
// also permit UpdateCurrentFrameIfStale to update the frame.
compositor()->SetVideoFrameProviderClient(nullptr);
// Advancing the tick clock should allow a new frame to be requested.
tick_clock_.Advance(base::TimeDelta::FromMilliseconds(10));
EXPECT_CALL(*this, Render(_, _, true)).WillOnce(Return(opaque_frame_2));
compositor()->UpdateCurrentFrameIfStale();
EXPECT_EQ(opaque_frame_2, compositor()->GetCurrentFrame());
// Background rendering should tick another render callback.
StopVideoRendererSink(false);
}
INSTANTIATE_TEST_SUITE_P(SubmitterEnabled,
VideoFrameCompositorTest,
::testing::Bool());
} // namespace media