1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/remoting/renderer_controller.h"
10 #include "base/functional/callback.h"
11 #include "base/run_loop.h"
12 #include "base/test/simple_test_tick_clock.h"
13 #include "base/test/task_environment.h"
14 #include "build/build_config.h"
15 #include "media/base/audio_decoder_config.h"
16 #include "media/base/limits.h"
17 #include "media/base/media_util.h"
18 #include "media/base/test_helpers.h"
19 #include "media/base/video_decoder_config.h"
20 #include "media/remoting/fake_remoter.h"
21 #include "testing/gtest/include/gtest/gtest.h"
28 PipelineMetadata DefaultMetadata(VideoCodec codec) {
29 PipelineMetadata data;
30 data.has_audio = true;
31 data.has_video = true;
32 data.video_decoder_config = TestVideoConfig::Normal(codec);
33 data.audio_decoder_config = TestAudioConfig::Normal();
34 data.natural_size = gfx::Size(1920, 1080);
38 const char kDefaultReceiver[] = "TestingChromeCast";
40 mojom::RemotingSinkMetadataPtr GetDefaultSinkMetadata(bool enable) {
41 mojom::RemotingSinkMetadataPtr metadata = mojom::RemotingSinkMetadata::New();
43 metadata->features.push_back(mojom::RemotingSinkFeature::RENDERING);
45 metadata->features.clear();
47 metadata->video_capabilities.push_back(
48 mojom::RemotingSinkVideoCapability::CODEC_VP8);
49 metadata->audio_capabilities.push_back(
50 mojom::RemotingSinkAudioCapability::CODEC_BASELINE_SET);
51 metadata->friendly_name = kDefaultReceiver;
55 constexpr base::TimeDelta kDelayedStartDuration = base::Seconds(5);
56 constexpr double frame_rate = 30;
57 constexpr double high_pixel_rate = 3840 * 2160 * 30;
60 class RendererControllerTest : public ::testing::Test,
61 public MediaObserverClient {
63 RendererControllerTest()
64 : controller_(FakeRemoterFactory::CreateController(false)) {}
66 RendererControllerTest(const RendererControllerTest&) = delete;
67 RendererControllerTest& operator=(const RendererControllerTest&) = delete;
69 ~RendererControllerTest() override = default;
71 void TearDown() final { RunUntilIdle(); }
73 static void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
75 // MediaObserverClient implementation.
76 void SwitchToRemoteRenderer(
77 const std::string& remote_device_friendly_name) override {
78 is_rendering_remotely_ = true;
79 disable_pipeline_suspend_ = true;
80 sink_name_ = remote_device_friendly_name;
83 void SwitchToLocalRenderer(ReasonToSwitchToLocal reason) override {
84 is_rendering_remotely_ = false;
85 disable_pipeline_suspend_ = false;
89 double Duration() const override { return duration_in_sec_; }
91 unsigned DecodedFrameCount() const override { return decoded_frames_; }
93 void UpdateRemotePlaybackCompatibility(bool is_compatible) override {
94 is_remote_playback_compatible_ = is_compatible;
97 void set_pixels_per_second_(double pixels) {
98 controller_->pixels_per_second_ = pixels;
101 void InitializeControllerWithSink(
102 const PipelineMetadata& pipeline_metadata,
103 mojom::RemotingSinkMetadataPtr sink_metadata) {
104 EXPECT_FALSE(is_rendering_remotely_);
105 EXPECT_TRUE(sink_name_.empty());
106 controller_->clock_ = &clock_;
107 clock_.Advance(base::Seconds(1));
108 controller_->SetClient(this);
109 controller_->OnSinkAvailable(std::move(sink_metadata));
110 controller_->OnRemotePlaybackDisabled(false);
111 controller_->OnMetadataChanged(pipeline_metadata);
112 controller_->OnPlaying();
114 PixelRateTimerEnds();
116 EXPECT_FALSE(is_rendering_remotely_);
117 EXPECT_FALSE(disable_pipeline_suspend_);
120 void InitializeControllerAndBecomeDominant(
121 const PipelineMetadata& pipeline_metadata,
122 mojom::RemotingSinkMetadataPtr sink_metadata) {
123 InitializeControllerWithSink(pipeline_metadata, std::move(sink_metadata));
124 controller_->OnBecameDominantVisibleContent(true);
128 void PixelRateTimerEnds() {
129 EXPECT_TRUE(controller_->pixel_rate_timer_.IsRunning());
130 decoded_frames_ = frame_rate * kDelayedStartDuration.InSeconds();
131 clock_.Advance(kDelayedStartDuration);
132 controller_->pixel_rate_timer_.FireNow();
135 void ExpectInRemoting() const {
136 EXPECT_TRUE(is_rendering_remotely_);
137 EXPECT_TRUE(disable_pipeline_suspend_);
138 EXPECT_EQ(kDefaultReceiver, sink_name_);
141 void ExpectInLocalRendering() const {
142 EXPECT_FALSE(is_rendering_remotely_);
143 EXPECT_FALSE(disable_pipeline_suspend_);
144 EXPECT_TRUE(sink_name_.empty());
147 bool ShouldBeRemoting() const { return controller_->ShouldBeRemoting(); }
149 base::test::SingleThreadTaskEnvironment task_environment_;
152 bool is_rendering_remotely_ = false;
153 bool disable_pipeline_suspend_ = false;
154 bool is_remote_playback_compatible_ = false;
155 size_t decoded_bytes_ = 0;
156 unsigned decoded_frames_ = 0;
157 base::SimpleTestTickClock clock_;
158 std::string sink_name_;
159 std::unique_ptr<RendererController> controller_;
160 double duration_in_sec_ = 120; // 2m duration.
163 TEST_F(RendererControllerTest, ShouldBeRemotingForDominantVisibleContent) {
164 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
165 GetDefaultSinkMetadata(true));
166 EXPECT_TRUE(ShouldBeRemoting());
168 controller_->OnBecameDominantVisibleContent(false);
170 EXPECT_FALSE(ShouldBeRemoting());
173 TEST_F(RendererControllerTest, ShouldBeRemotingForRequestFromBrowser) {
174 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
175 GetDefaultSinkMetadata(true));
176 controller_->OnMediaRemotingRequested();
178 EXPECT_TRUE(ShouldBeRemoting());
180 controller_->OnSinkGone();
182 EXPECT_FALSE(ShouldBeRemoting());
186 TEST_F(RendererControllerTest, ToggleRendererOnDominantChange) {
187 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
188 GetDefaultSinkMetadata(true));
189 ExpectInRemoting(); // All requirements now satisfied.
191 // Leaving fullscreen should shut down remoting.
192 controller_->OnBecameDominantVisibleContent(false);
194 ExpectInLocalRendering();
197 TEST_F(RendererControllerTest, ToggleRendererOnDisableChange) {
198 EXPECT_FALSE(is_rendering_remotely_);
199 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
200 GetDefaultSinkMetadata(true));
202 ExpectInRemoting(); // All requirements now satisfied.
204 // If the page disables remote playback (e.g., by setting the
205 // disableRemotePlayback attribute), this should shut down remoting.
206 controller_->OnRemotePlaybackDisabled(true);
208 ExpectInLocalRendering();
211 TEST_F(RendererControllerTest, NotStartForShortContent) {
212 duration_in_sec_ = 20;
213 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
214 GetDefaultSinkMetadata(true));
215 ExpectInLocalRendering();
218 TEST_F(RendererControllerTest, ToggleRendererOnSinkCapabilities) {
219 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
220 GetDefaultSinkMetadata(false));
221 // An available sink that does not support remote rendering should not cause
222 // the controller to toggle remote rendering on.
223 ExpectInLocalRendering();
224 controller_->OnSinkGone(); // Bye-bye useless sink!
226 ExpectInLocalRendering();
228 // A sink that *does* support remote rendering *does* cause the controller to
229 // toggle remote rendering on.
230 controller_->OnSinkAvailable(GetDefaultSinkMetadata(true));
233 ExpectInRemoting(); // All requirements now satisfied.
236 TEST_F(RendererControllerTest, ToggleRendererOnMediaRemotingRequest) {
237 InitializeControllerWithSink(DefaultMetadata(VideoCodec::kVP8), nullptr);
238 ExpectInLocalRendering();
240 // Should not start media remoting when there is no sink.
241 controller_->OnMediaRemotingRequested();
243 ExpectInLocalRendering();
245 // Start media remoting when there are available sinks.
246 controller_->OnSinkAvailable(GetDefaultSinkMetadata(false));
252 TEST_F(RendererControllerTest, WithVP9VideoCodec) {
253 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP9),
254 GetDefaultSinkMetadata(true));
255 // An available sink that does not support VP9 video codec should not cause
256 // the controller to toggle remote rendering on.
257 ExpectInLocalRendering();
259 controller_->OnSinkGone(); // Bye-bye useless sink!
260 mojom::RemotingSinkMetadataPtr sink_metadata = GetDefaultSinkMetadata(true);
261 sink_metadata->video_capabilities.push_back(
262 mojom::RemotingSinkVideoCapability::CODEC_VP9);
263 // A sink that *does* support VP9 video codec *does* cause the controller to
264 // toggle remote rendering on.
265 controller_->OnSinkAvailable(std::move(sink_metadata));
268 ExpectInRemoting(); // All requirements now satisfied.
271 TEST_F(RendererControllerTest, WithHEVCVideoCodec) {
272 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kHEVC),
273 GetDefaultSinkMetadata(true));
274 // An available sink that does not support HEVC video codec should not cause
275 // the controller to toggle remote rendering on.
276 ExpectInLocalRendering();
278 controller_->OnSinkGone(); // Bye-bye useless sink!
280 ExpectInLocalRendering();
281 mojom::RemotingSinkMetadataPtr sink_metadata = GetDefaultSinkMetadata(true);
282 sink_metadata->video_capabilities.push_back(
283 mojom::RemotingSinkVideoCapability::CODEC_HEVC);
284 // A sink that *does* support HEVC video codec *does* cause the controller to
285 // toggle remote rendering on.
286 controller_->OnSinkAvailable(std::move(sink_metadata));
289 ExpectInRemoting(); // All requirements now satisfied.
292 TEST_F(RendererControllerTest, WithAACAudioCodec) {
293 const AudioDecoderConfig audio_config = AudioDecoderConfig(
294 AudioCodec::kAAC, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 44100,
295 EmptyExtraData(), EncryptionScheme::kUnencrypted);
296 PipelineMetadata pipeline_metadata = DefaultMetadata(VideoCodec::kVP8);
297 pipeline_metadata.audio_decoder_config = audio_config;
298 InitializeControllerAndBecomeDominant(pipeline_metadata,
299 GetDefaultSinkMetadata(true));
300 // An available sink that does not support AAC audio codec should not cause
301 // the controller to toggle remote rendering on.
302 ExpectInLocalRendering();
304 controller_->OnSinkGone(); // Bye-bye useless sink!
306 ExpectInLocalRendering();
307 mojom::RemotingSinkMetadataPtr sink_metadata = GetDefaultSinkMetadata(true);
308 sink_metadata->audio_capabilities.push_back(
309 mojom::RemotingSinkAudioCapability::CODEC_AAC);
310 // A sink that *does* support AAC audio codec *does* cause the controller to
311 // toggle remote rendering on.
312 controller_->OnSinkAvailable(std::move(sink_metadata));
315 ExpectInRemoting(); // All requirements now satisfied.
318 TEST_F(RendererControllerTest, WithOpusAudioCodec) {
319 const AudioDecoderConfig audio_config = AudioDecoderConfig(
320 AudioCodec::kOpus, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 44100,
321 EmptyExtraData(), EncryptionScheme::kUnencrypted);
322 PipelineMetadata pipeline_metadata = DefaultMetadata(VideoCodec::kVP8);
323 pipeline_metadata.audio_decoder_config = audio_config;
324 InitializeControllerAndBecomeDominant(pipeline_metadata,
325 GetDefaultSinkMetadata(true));
326 // An available sink that does not support Opus audio codec should not cause
327 // the controller to toggle remote rendering on.
328 ExpectInLocalRendering();
330 controller_->OnSinkGone(); // Bye-bye useless sink!
332 mojom::RemotingSinkMetadataPtr sink_metadata = GetDefaultSinkMetadata(true);
333 sink_metadata->audio_capabilities.push_back(
334 mojom::RemotingSinkAudioCapability::CODEC_OPUS);
335 // A sink that *does* support Opus audio codec *does* cause the controller to
336 // toggle remote rendering on.
337 controller_->OnSinkAvailable(std::move(sink_metadata));
340 ExpectInRemoting(); // All requirements now satisfied.
343 TEST_F(RendererControllerTest, StartFailedWithHighPixelRate) {
344 InitializeControllerWithSink(DefaultMetadata(VideoCodec::kVP8),
345 GetDefaultSinkMetadata(true));
346 set_pixels_per_second_(high_pixel_rate);
348 controller_->OnBecameDominantVisibleContent(true);
350 ExpectInLocalRendering();
353 TEST_F(RendererControllerTest, StartSuccessWithHighPixelRate) {
354 mojom::RemotingSinkMetadataPtr sink_metadata = GetDefaultSinkMetadata(true);
355 sink_metadata->video_capabilities.push_back(
356 mojom::RemotingSinkVideoCapability::SUPPORT_4K);
357 InitializeControllerWithSink(DefaultMetadata(VideoCodec::kVP8),
358 std::move(sink_metadata));
359 set_pixels_per_second_(high_pixel_rate);
361 controller_->OnBecameDominantVisibleContent(true);
366 TEST_F(RendererControllerTest, PacingTooSlowly) {
367 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
368 GetDefaultSinkMetadata(true));
370 ExpectInRemoting(); // All requirements now satisfied.
371 controller_->OnRendererFatalError(StopTrigger::PACING_TOO_SLOWLY);
373 ExpectInLocalRendering();
374 controller_->OnSinkAvailable(GetDefaultSinkMetadata(true));
376 controller_->OnBecameDominantVisibleContent(false);
378 ExpectInLocalRendering();
379 controller_->OnBecameDominantVisibleContent(true);
381 ExpectInRemoting(); // All requirements now satisfied.
384 TEST_F(RendererControllerTest, StartFailed) {
385 controller_ = FakeRemoterFactory::CreateController(true);
386 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
387 GetDefaultSinkMetadata(true));
389 ExpectInLocalRendering();
392 TEST_F(RendererControllerTest, SetClientNullptr) {
393 controller_ = FakeRemoterFactory::CreateController(true);
394 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
395 GetDefaultSinkMetadata(true));
396 controller_->SetClient(nullptr);
398 ExpectInLocalRendering();
401 TEST_F(RendererControllerTest, OnFrozen) {
402 InitializeControllerAndBecomeDominant(DefaultMetadata(VideoCodec::kVP8),
403 GetDefaultSinkMetadata(true));
408 // Pausing needs to occur before freezing can be enabled.
409 controller_->OnPaused();
412 // Freezing should kick rendering back to local.
413 controller_->OnFrozen();
415 ExpectInLocalRendering();
418 #if BUILDFLAG(IS_ANDROID)
419 TEST_F(RendererControllerTest, RemotePlaybackHlsCompatibility) {
420 controller_ = FakeRemoterFactory::CreateController(true);
421 controller_->SetClient(this);
423 controller_->OnDataSourceInitialized(GURL("http://example.com/foo.m3u8"));
425 PipelineMetadata incompatible_metadata;
426 incompatible_metadata.has_video = false;
427 incompatible_metadata.has_audio = false;
428 controller_->OnMetadataChanged(incompatible_metadata);
429 EXPECT_FALSE(is_remote_playback_compatible_);
431 // HLS is compatible with RemotePlayback regardless of the metadata we have.
432 controller_->OnHlsManifestDetected();
433 EXPECT_TRUE(is_remote_playback_compatible_);
437 } // namespace remoting