| // 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 "media/remoting/courier_renderer.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "base/run_loop.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "media/base/media_util.h" |
| #include "media/base/pipeline_status.h" |
| #include "media/base/renderer_client.h" |
| #include "media/base/test_helpers.h" |
| #include "media/remoting/fake_media_resource.h" |
| #include "media/remoting/fake_remoter.h" |
| #include "media/remoting/proto_enum_utils.h" |
| #include "media/remoting/proto_utils.h" |
| #include "media/remoting/renderer_controller.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/openscreen/src/cast/streaming/rpc_messenger.h" |
| |
| using openscreen::cast::RpcMessenger; |
| using testing::_; |
| using testing::Invoke; |
| using testing::Return; |
| |
| namespace media { |
| namespace remoting { |
| |
| namespace { |
| |
| PipelineMetadata DefaultMetadata() { |
| PipelineMetadata data; |
| data.has_audio = true; |
| data.has_video = true; |
| data.video_decoder_config = TestVideoConfig::Normal(); |
| return data; |
| } |
| |
| PipelineStatistics DefaultStats() { |
| PipelineStatistics stats; |
| stats.audio_bytes_decoded = 1234U; |
| stats.video_bytes_decoded = 2345U; |
| stats.video_frames_decoded = 3000U; |
| stats.video_frames_dropped = 91U; |
| stats.audio_memory_usage = 5678; |
| stats.video_memory_usage = 6789; |
| stats.video_keyframe_distance_average = base::TimeDelta::Max(); |
| stats.audio_pipeline_info = {false, false, AudioDecoderType::kUnknown, |
| EncryptionType::kClear}; |
| stats.video_pipeline_info = {false, false, VideoDecoderType::kUnknown, |
| EncryptionType::kClear}; |
| return stats; |
| } |
| |
| class RendererClientImpl final : public RendererClient { |
| public: |
| RendererClientImpl() { |
| ON_CALL(*this, OnStatisticsUpdate(_)) |
| .WillByDefault( |
| Invoke(this, &RendererClientImpl::DelegateOnStatisticsUpdate)); |
| ON_CALL(*this, OnPipelineStatus(_)) |
| .WillByDefault( |
| Invoke(this, &RendererClientImpl::DelegateOnPipelineStatus)); |
| ON_CALL(*this, OnBufferingStateChange(_, _)) |
| .WillByDefault( |
| Invoke(this, &RendererClientImpl::DelegateOnBufferingStateChange)); |
| ON_CALL(*this, OnAudioConfigChange(_)) |
| .WillByDefault( |
| Invoke(this, &RendererClientImpl::DelegateOnAudioConfigChange)); |
| ON_CALL(*this, OnVideoConfigChange(_)) |
| .WillByDefault( |
| Invoke(this, &RendererClientImpl::DelegateOnVideoConfigChange)); |
| ON_CALL(*this, OnVideoNaturalSizeChange(_)) |
| .WillByDefault(Invoke( |
| this, &RendererClientImpl::DelegateOnVideoNaturalSizeChange)); |
| ON_CALL(*this, OnVideoOpacityChange(_)) |
| .WillByDefault( |
| Invoke(this, &RendererClientImpl::DelegateOnVideoOpacityChange)); |
| } |
| |
| RendererClientImpl(const RendererClientImpl&) = delete; |
| RendererClientImpl& operator=(const RendererClientImpl&) = delete; |
| |
| ~RendererClientImpl() = default; |
| |
| // RendererClient implementation. |
| void OnError(PipelineStatus status) override {} |
| void OnEnded() override {} |
| MOCK_METHOD1(OnStatisticsUpdate, void(const PipelineStatistics& stats)); |
| MOCK_METHOD2(OnBufferingStateChange, |
| void(BufferingState state, BufferingStateChangeReason reason)); |
| MOCK_METHOD1(OnAudioConfigChange, void(const AudioDecoderConfig& config)); |
| MOCK_METHOD1(OnVideoConfigChange, void(const VideoDecoderConfig& config)); |
| void OnWaiting(WaitingReason reason) override {} |
| MOCK_METHOD1(OnVideoNaturalSizeChange, void(const gfx::Size& size)); |
| MOCK_METHOD1(OnVideoOpacityChange, void(bool opaque)); |
| MOCK_METHOD1(OnVideoFrameRateChange, void(absl::optional<int>)); |
| MOCK_METHOD1(OnRemotePlayStateChange, void(MediaStatus::State state)); |
| |
| void DelegateOnStatisticsUpdate(const PipelineStatistics& stats) { |
| stats_ = stats; |
| } |
| void DelegateOnBufferingStateChange(BufferingState state, |
| BufferingStateChangeReason reason) { |
| state_ = state; |
| } |
| void DelegateOnAudioConfigChange(const AudioDecoderConfig& config) { |
| audio_decoder_config_ = config; |
| } |
| void DelegateOnVideoConfigChange(const VideoDecoderConfig& config) { |
| video_decoder_config_ = config; |
| } |
| void DelegateOnVideoNaturalSizeChange(const gfx::Size& size) { size_ = size; } |
| void DelegateOnVideoOpacityChange(bool opaque) { opaque_ = opaque; } |
| |
| MOCK_METHOD1(OnPipelineStatus, void(PipelineStatus status)); |
| void DelegateOnPipelineStatus(PipelineStatus status) { |
| VLOG(2) << "OnPipelineStatus status:" << status; |
| status_ = status; |
| } |
| MOCK_METHOD0(OnFlushCallback, void()); |
| |
| PipelineStatus status() const { return status_; } |
| PipelineStatistics stats() const { return stats_; } |
| BufferingState state() const { return state_; } |
| gfx::Size size() const { return size_; } |
| bool opaque() const { return opaque_; } |
| VideoDecoderConfig video_decoder_config() const { |
| return video_decoder_config_; |
| } |
| AudioDecoderConfig audio_decoder_config() const { |
| return audio_decoder_config_; |
| } |
| |
| private: |
| PipelineStatus status_ = PIPELINE_OK; |
| BufferingState state_ = BUFFERING_HAVE_NOTHING; |
| gfx::Size size_; |
| bool opaque_ = false; |
| PipelineStatistics stats_; |
| VideoDecoderConfig video_decoder_config_; |
| AudioDecoderConfig audio_decoder_config_; |
| }; |
| |
| } // namespace |
| |
| class CourierRendererTest : public testing::Test { |
| public: |
| CourierRendererTest() = default; |
| |
| CourierRendererTest(const CourierRendererTest&) = delete; |
| CourierRendererTest& operator=(const CourierRendererTest&) = delete; |
| |
| ~CourierRendererTest() override = default; |
| |
| // Use this function to mimic receiver to handle RPC message for renderer |
| // initialization, |
| void RpcMessageResponseBot(std::vector<uint8_t> message) { |
| openscreen::cast::RpcMessage rpc; |
| ASSERT_TRUE(rpc.ParseFromArray(message.data(), message.size())); |
| switch (rpc.proc()) { |
| case openscreen::cast::RpcMessage::RPC_ACQUIRE_RENDERER: { |
| DCHECK(rpc.has_integer_value()); |
| sender_renderer_handle_ = rpc.integer_value(); |
| // Issues RPC_ACQUIRE_RENDERER_DONE RPC message. |
| auto acquire_done = std::make_unique<openscreen::cast::RpcMessage>(); |
| acquire_done->set_handle(sender_renderer_handle_); |
| acquire_done->set_proc( |
| openscreen::cast::RpcMessage::RPC_ACQUIRE_RENDERER_DONE); |
| acquire_done->set_integer_value(receiver_renderer_handle_); |
| controller_->GetRpcMessenger()->ProcessMessageFromRemote( |
| std::move(acquire_done)); |
| } break; |
| case openscreen::cast::RpcMessage::RPC_ACQUIRE_DEMUXER: { |
| if (!is_backward_compatible_mode_) { |
| int acquire_demuxer_handle = RpcMessenger::kAcquireDemuxerHandle; |
| EXPECT_EQ(rpc.handle(), acquire_demuxer_handle); |
| sender_audio_demuxer_handle_ = |
| rpc.acquire_demuxer_rpc().audio_demuxer_handle(); |
| sender_video_demuxer_handle_ = |
| rpc.acquire_demuxer_rpc().video_demuxer_handle(); |
| |
| // Issues audio RPC_DS_INITIALIZE RPC message. |
| if (sender_audio_demuxer_handle_ != RpcMessenger::kInvalidHandle) { |
| auto ds_init = std::make_unique<openscreen::cast::RpcMessage>(); |
| ds_init->set_handle(sender_audio_demuxer_handle_); |
| ds_init->set_proc(openscreen::cast::RpcMessage::RPC_DS_INITIALIZE); |
| ds_init->set_integer_value(receiver_audio_demuxer_callback_handle_); |
| controller_->GetRpcMessenger()->ProcessMessageFromRemote( |
| std::move(ds_init)); |
| } |
| |
| // Issues video RPC_DS_INITIALIZE RPC message. |
| if (sender_video_demuxer_handle_ != RpcMessenger::kInvalidHandle) { |
| auto ds_init = std::make_unique<openscreen::cast::RpcMessage>(); |
| ds_init->set_handle(sender_video_demuxer_handle_); |
| ds_init->set_proc(openscreen::cast::RpcMessage::RPC_DS_INITIALIZE); |
| ds_init->set_integer_value(receiver_video_demuxer_callback_handle_); |
| controller_->GetRpcMessenger()->ProcessMessageFromRemote( |
| std::move(ds_init)); |
| } |
| } |
| } break; |
| case openscreen::cast::RpcMessage::RPC_R_INITIALIZE: { |
| sender_renderer_callback_handle_ = |
| rpc.renderer_initialize_rpc().callback_handle(); |
| sender_client_handle_ = rpc.renderer_initialize_rpc().client_handle(); |
| |
| if (is_backward_compatible_mode_) { |
| EXPECT_EQ(rpc.handle(), receiver_renderer_handle_); |
| |
| sender_audio_demuxer_handle_ = |
| rpc.renderer_initialize_rpc().audio_demuxer_handle(); |
| sender_video_demuxer_handle_ = |
| rpc.renderer_initialize_rpc().video_demuxer_handle(); |
| |
| // Issues audio RPC_DS_INITIALIZE RPC message. |
| if (sender_audio_demuxer_handle_ != RpcMessenger::kInvalidHandle) { |
| auto ds_init = std::make_unique<openscreen::cast::RpcMessage>(); |
| ds_init->set_handle(sender_audio_demuxer_handle_); |
| ds_init->set_proc(openscreen::cast::RpcMessage::RPC_DS_INITIALIZE); |
| ds_init->set_integer_value(receiver_audio_demuxer_callback_handle_); |
| controller_->GetRpcMessenger()->ProcessMessageFromRemote( |
| std::move(ds_init)); |
| } |
| |
| // Issues video RPC_DS_INITIALIZE RPC message. |
| if (sender_video_demuxer_handle_ != RpcMessenger::kInvalidHandle) { |
| auto ds_init = std::make_unique<openscreen::cast::RpcMessage>(); |
| ds_init->set_handle(sender_video_demuxer_handle_); |
| ds_init->set_proc(openscreen::cast::RpcMessage::RPC_DS_INITIALIZE); |
| ds_init->set_integer_value(receiver_video_demuxer_callback_handle_); |
| controller_->GetRpcMessenger()->ProcessMessageFromRemote( |
| std::move(ds_init)); |
| } |
| } else { |
| // Issues RPC_R_INITIALIZE_CALLBACK RPC message when receiving |
| // RPC_R_INITIALIZE. |
| auto init_cb = std::make_unique<openscreen::cast::RpcMessage>(); |
| init_cb->set_handle(sender_renderer_callback_handle_); |
| init_cb->set_proc( |
| openscreen::cast::RpcMessage::RPC_R_INITIALIZE_CALLBACK); |
| init_cb->set_boolean_value(is_successfully_initialized_); |
| controller_->GetRpcMessenger()->ProcessMessageFromRemote( |
| std::move(init_cb)); |
| } |
| } break; |
| case openscreen::cast::RpcMessage::RPC_DS_INITIALIZE_CALLBACK: { |
| if (rpc.handle() == receiver_audio_demuxer_callback_handle_) |
| received_audio_ds_init_cb_ = true; |
| if (rpc.handle() == receiver_video_demuxer_callback_handle_) |
| received_video_ds_init_cb_ = true; |
| |
| // Check whether the demuxer at the receiver end is initialized. |
| if (received_audio_ds_init_cb_ == (sender_audio_demuxer_handle_ != |
| RpcMessenger::kInvalidHandle) && |
| received_video_ds_init_cb_ == (sender_video_demuxer_handle_ != |
| RpcMessenger::kInvalidHandle)) { |
| is_receiver_demuxer_initialized_ = true; |
| } |
| |
| if (is_backward_compatible_mode_ && is_receiver_demuxer_initialized_) { |
| // Issues RPC_R_INITIALIZE_CALLBACK RPC message when receiving |
| // RPC_DS_INITIALIZE_CALLBACK on available streams. |
| auto init_cb = std::make_unique<openscreen::cast::RpcMessage>(); |
| init_cb->set_handle(sender_renderer_callback_handle_); |
| init_cb->set_proc( |
| openscreen::cast::RpcMessage::RPC_R_INITIALIZE_CALLBACK); |
| init_cb->set_boolean_value(is_successfully_initialized_); |
| controller_->GetRpcMessenger()->ProcessMessageFromRemote( |
| std::move(init_cb)); |
| } |
| } break; |
| case openscreen::cast::RpcMessage::RPC_R_FLUSHUNTIL: { |
| // Issues RPC_R_FLUSHUNTIL_CALLBACK RPC message. |
| std::unique_ptr<openscreen::cast::RpcMessage> flush_cb( |
| new openscreen::cast::RpcMessage()); |
| flush_cb->set_handle(rpc.renderer_flushuntil_rpc().callback_handle()); |
| flush_cb->set_proc( |
| openscreen::cast::RpcMessage::RPC_R_FLUSHUNTIL_CALLBACK); |
| controller_->GetRpcMessenger()->ProcessMessageFromRemote( |
| std::move(flush_cb)); |
| } break; |
| case openscreen::cast::RpcMessage::RPC_R_SETVOLUME: |
| // No response needed. |
| break; |
| |
| default: |
| NOTREACHED(); |
| } |
| RunPendingTasks(); |
| } |
| |
| // Callback from RpcMessenger when sending message to remote sink. |
| void OnSendMessageToSink(std::vector<uint8_t> message) { |
| openscreen::cast::RpcMessage rpc; |
| ASSERT_TRUE(rpc.ParseFromArray(message.data(), message.size())); |
| received_rpc_.push_back(std::move(rpc)); |
| } |
| |
| void RewireSendMessageCallbackToSink() { |
| controller_->GetRpcMessenger()->set_send_message_cb_for_testing( |
| [this](std::vector<uint8_t> message) { |
| this->OnSendMessageToSink(message); |
| }); |
| } |
| |
| protected: |
| void InitializeRenderer() { |
| // Register media::RendererClient implementation. |
| render_client_ = std::make_unique<RendererClientImpl>(); |
| media_resource_ = std::make_unique<FakeMediaResource>(); |
| EXPECT_CALL(*render_client_, OnPipelineStatus(_)).Times(1); |
| DCHECK(renderer_); |
| // Redirect RPC message for simulate receiver scenario |
| controller_->GetRpcMessenger()->set_send_message_cb_for_testing( |
| [this](std::vector<uint8_t> message) { |
| this->RpcMessageResponseBot(message); |
| }); |
| RunPendingTasks(); |
| renderer_->Initialize( |
| media_resource_.get(), render_client_.get(), |
| base::BindOnce(&RendererClientImpl::OnPipelineStatus, |
| base::Unretained(render_client_.get()))); |
| RunPendingTasks(); |
| RewireSendMessageCallbackToSink(); |
| RunPendingTasks(); |
| } |
| |
| void InitializeRendererBackwardsCompatible() { |
| is_backward_compatible_mode_ = true; |
| InitializeRenderer(); |
| } |
| |
| bool IsRendererInitialized() const { |
| EXPECT_TRUE(received_audio_ds_init_cb_); |
| EXPECT_TRUE(received_video_ds_init_cb_); |
| return renderer_->state_ == CourierRenderer::STATE_PLAYING && |
| is_receiver_demuxer_initialized_; |
| } |
| |
| bool DidEncounterFatalError() const { |
| return renderer_->state_ == CourierRenderer::STATE_ERROR; |
| } |
| |
| void OnReceivedRpc(std::unique_ptr<openscreen::cast::RpcMessage> message) { |
| renderer_->OnReceivedRpc(std::move(message)); |
| } |
| |
| void SetUp() override { |
| controller_ = FakeRemoterFactory::CreateController(false); |
| controller_->OnMetadataChanged(DefaultMetadata()); |
| |
| RewireSendMessageCallbackToSink(); |
| renderer_ = |
| std::make_unique<CourierRenderer>(base::ThreadTaskRunnerHandle::Get(), |
| controller_->GetWeakPtr(), nullptr); |
| renderer_->clock_ = &clock_; |
| clock_.Advance(base::Seconds(1)); |
| |
| RunPendingTasks(); |
| } |
| |
| CourierRenderer::State state() const { return renderer_->state_; } |
| |
| void RunPendingTasks() { base::RunLoop().RunUntilIdle(); } |
| |
| // Gets first available RpcMessage with specific |proc|. |
| const openscreen::cast::RpcMessage* PeekRpcMessage(int proc) const { |
| for (auto& s : received_rpc_) { |
| if (proc == s.proc()) |
| return &s; |
| } |
| |
| return nullptr; |
| } |
| int ReceivedRpcMessageCount() const { return received_rpc_.size(); } |
| void ResetReceivedRpcMessage() { received_rpc_.clear(); } |
| |
| void ValidateCurrentTime(base::TimeDelta current, |
| base::TimeDelta current_max) const { |
| ASSERT_EQ(renderer_->current_media_time_, current); |
| ASSERT_EQ(renderer_->current_max_time_, current_max); |
| } |
| |
| // Issues RPC_RC_ONTIMEUPDATE RPC message. |
| void IssueTimeUpdateRpc(base::TimeDelta media_time, |
| base::TimeDelta max_media_time) { |
| std::unique_ptr<openscreen::cast::RpcMessage> rpc( |
| new openscreen::cast::RpcMessage()); |
| rpc->set_handle(5); |
| rpc->set_proc(openscreen::cast::RpcMessage::RPC_RC_ONTIMEUPDATE); |
| auto* time_message = rpc->mutable_rendererclient_ontimeupdate_rpc(); |
| time_message->set_time_usec(media_time.InMicroseconds()); |
| time_message->set_max_time_usec(max_media_time.InMicroseconds()); |
| OnReceivedRpc(std::move(rpc)); |
| RunPendingTasks(); |
| } |
| |
| // Verifies no error reported and issues a series of time updates RPC |
| // messages. No verification after the last message is issued. |
| void VerifyAndReportTimeUpdates(int start_serial_number, |
| int end_serial_number) { |
| for (int i = start_serial_number; i < end_serial_number; ++i) { |
| ASSERT_FALSE(DidEncounterFatalError()); |
| IssueTimeUpdateRpc(base::Milliseconds(100 + i * 800), base::Seconds(100)); |
| clock_.Advance(base::Seconds(1)); |
| RunPendingTasks(); |
| } |
| } |
| |
| // Issues RPC_RC_ONSTATISTICSUPDATE RPC message with DefaultStats(). |
| void IssueStatisticsUpdateRpc() { |
| EXPECT_CALL(*render_client_, OnStatisticsUpdate(_)).Times(1); |
| const PipelineStatistics stats = DefaultStats(); |
| std::unique_ptr<openscreen::cast::RpcMessage> rpc( |
| new openscreen::cast::RpcMessage()); |
| rpc->set_handle(5); |
| rpc->set_proc(openscreen::cast::RpcMessage::RPC_RC_ONSTATISTICSUPDATE); |
| auto* message = rpc->mutable_rendererclient_onstatisticsupdate_rpc(); |
| message->set_audio_bytes_decoded(stats.audio_bytes_decoded); |
| message->set_video_bytes_decoded(stats.video_bytes_decoded); |
| message->set_video_frames_decoded(stats.video_frames_decoded); |
| message->set_video_frames_dropped(stats.video_frames_dropped); |
| message->set_audio_memory_usage(stats.audio_memory_usage); |
| message->set_video_memory_usage(stats.video_memory_usage); |
| message->mutable_audio_decoder_info()->set_is_platform_decoder( |
| stats.audio_pipeline_info.is_platform_decoder); |
| message->mutable_audio_decoder_info()->set_decoder_type( |
| static_cast<int64_t>(stats.audio_pipeline_info.decoder_type)); |
| message->mutable_video_decoder_info()->set_is_platform_decoder( |
| stats.video_pipeline_info.is_platform_decoder); |
| message->mutable_video_decoder_info()->set_decoder_type( |
| static_cast<int64_t>(stats.video_pipeline_info.decoder_type)); |
| OnReceivedRpc(std::move(rpc)); |
| RunPendingTasks(); |
| } |
| |
| // Issue RPC_RC_ONBUFFERINGSTATECHANGE RPC message. |
| void IssuesBufferingStateRpc(BufferingState state) { |
| absl::optional< |
| openscreen::cast::RendererClientOnBufferingStateChange::State> |
| pb_state = ToProtoMediaBufferingState(state); |
| if (!pb_state.has_value()) |
| return; |
| std::unique_ptr<openscreen::cast::RpcMessage> rpc( |
| new openscreen::cast::RpcMessage()); |
| rpc->set_handle(5); |
| rpc->set_proc(openscreen::cast::RpcMessage::RPC_RC_ONBUFFERINGSTATECHANGE); |
| auto* buffering_state = |
| rpc->mutable_rendererclient_onbufferingstatechange_rpc(); |
| buffering_state->set_state(pb_state.value()); |
| OnReceivedRpc(std::move(rpc)); |
| RunPendingTasks(); |
| } |
| |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| std::unique_ptr<RendererController> controller_; |
| std::unique_ptr<RendererClientImpl> render_client_; |
| std::unique_ptr<FakeMediaResource> media_resource_; |
| std::unique_ptr<CourierRenderer> renderer_; |
| base::SimpleTestTickClock clock_; |
| |
| // RPC handles. |
| const int receiver_renderer_handle_{10}; |
| const int receiver_audio_demuxer_callback_handle_{11}; |
| const int receiver_video_demuxer_callback_handle_{12}; |
| int sender_renderer_handle_; |
| int sender_client_handle_{RpcMessenger::kInvalidHandle}; |
| int sender_renderer_callback_handle_{RpcMessenger::kInvalidHandle}; |
| int sender_audio_demuxer_handle_{RpcMessenger::kInvalidHandle}; |
| int sender_video_demuxer_handle_{RpcMessenger::kInvalidHandle}; |
| |
| // Indicates whether the test runs in backward-compatible mode. |
| bool is_backward_compatible_mode_ = false; |
| |
| // Indicates whether the demuxer at receiver is initialized or not. |
| bool is_receiver_demuxer_initialized_ = false; |
| |
| // Indicate whether RPC_DS_INITIALIZE_CALLBACK RPC messages are received. |
| bool received_audio_ds_init_cb_ = false; |
| bool received_video_ds_init_cb_ = false; |
| |
| // Indicates whether the test wants to simulate successful initialization in |
| // the renderer on the receiver side. |
| bool is_successfully_initialized_ = true; |
| |
| // Stores RPC messages that have been sent to the remote sink. |
| std::vector<openscreen::cast::RpcMessage> received_rpc_; |
| }; |
| |
| TEST_F(CourierRendererTest, Initialize) { |
| InitializeRenderer(); |
| RunPendingTasks(); |
| |
| ASSERT_TRUE(IsRendererInitialized()); |
| ASSERT_EQ(render_client_->status(), PIPELINE_OK); |
| } |
| |
| TEST_F(CourierRendererTest, InitializeBackwardCompatible) { |
| InitializeRendererBackwardsCompatible(); |
| RunPendingTasks(); |
| |
| ASSERT_TRUE(IsRendererInitialized()); |
| ASSERT_EQ(render_client_->status(), PIPELINE_OK); |
| } |
| |
| TEST_F(CourierRendererTest, InitializeFailed) { |
| is_successfully_initialized_ = false; |
| InitializeRenderer(); |
| RunPendingTasks(); |
| ASSERT_FALSE(IsRendererInitialized()); |
| ASSERT_TRUE(DidEncounterFatalError()); |
| // Don't report error to prevent breaking the pipeline. |
| ASSERT_EQ(render_client_->status(), PIPELINE_OK); |
| |
| // The CourierRenderer should act as a no-op renderer from this point. |
| |
| ResetReceivedRpcMessage(); |
| EXPECT_CALL(*render_client_, OnFlushCallback()).Times(1); |
| renderer_->Flush(base::BindOnce(&RendererClientImpl::OnFlushCallback, |
| base::Unretained(render_client_.get()))); |
| RunPendingTasks(); |
| ASSERT_EQ(0, ReceivedRpcMessageCount()); |
| |
| base::TimeDelta seek = base::Microseconds(100); |
| renderer_->StartPlayingFrom(seek); |
| RunPendingTasks(); |
| ASSERT_EQ(0, ReceivedRpcMessageCount()); |
| |
| renderer_->SetVolume(3.0); |
| RunPendingTasks(); |
| ASSERT_EQ(0, ReceivedRpcMessageCount()); |
| |
| renderer_->SetPlaybackRate(2.5); |
| RunPendingTasks(); |
| ASSERT_EQ(0, ReceivedRpcMessageCount()); |
| } |
| |
| TEST_F(CourierRendererTest, Flush) { |
| // Initialize Renderer. |
| InitializeRenderer(); |
| RunPendingTasks(); |
| ASSERT_TRUE(IsRendererInitialized()); |
| ASSERT_EQ(render_client_->status(), PIPELINE_OK); |
| |
| // Flush Renderer. |
| // Redirect RPC message for simulate receiver scenario |
| controller_->GetRpcMessenger()->set_send_message_cb_for_testing( |
| [this](std::vector<uint8_t> message) { |
| this->RpcMessageResponseBot(message); |
| }); |
| RunPendingTasks(); |
| EXPECT_CALL(*render_client_, OnFlushCallback()).Times(1); |
| renderer_->Flush(base::BindOnce(&RendererClientImpl::OnFlushCallback, |
| base::Unretained(render_client_.get()))); |
| RunPendingTasks(); |
| } |
| |
| TEST_F(CourierRendererTest, StartPlayingFrom) { |
| // Initialize Renderer |
| InitializeRenderer(); |
| RunPendingTasks(); |
| ASSERT_TRUE(IsRendererInitialized()); |
| ASSERT_EQ(render_client_->status(), PIPELINE_OK); |
| |
| // StartPlaying from |
| base::TimeDelta seek = base::Microseconds(100); |
| renderer_->StartPlayingFrom(seek); |
| RunPendingTasks(); |
| |
| // Checks if it sends out RPC message with correct value. |
| ASSERT_EQ(1, ReceivedRpcMessageCount()); |
| const openscreen::cast::RpcMessage* rpc = |
| PeekRpcMessage(openscreen::cast::RpcMessage::RPC_R_STARTPLAYINGFROM); |
| ASSERT_TRUE(rpc); |
| ASSERT_EQ(100, rpc->integer64_value()); |
| } |
| |
| TEST_F(CourierRendererTest, SetVolume) { |
| // Initialize Renderer because, as of this writing, the pipeline guarantees it |
| // will not call SetVolume() until after the media::Renderer is initialized. |
| InitializeRenderer(); |
| RunPendingTasks(); |
| ASSERT_EQ(0, ReceivedRpcMessageCount()); |
| |
| // SetVolume() will send openscreen::cast::RpcMessage::RPC_R_SETVOLUME RPC. |
| renderer_->SetVolume(3.0); |
| RunPendingTasks(); |
| |
| // Checks if it sends out RPC message with correct value. |
| ASSERT_EQ(1, ReceivedRpcMessageCount()); |
| const openscreen::cast::RpcMessage* rpc = |
| PeekRpcMessage(openscreen::cast::RpcMessage::RPC_R_SETVOLUME); |
| ASSERT_TRUE(rpc); |
| ASSERT_DOUBLE_EQ(3.0, rpc->double_value()); |
| } |
| |
| TEST_F(CourierRendererTest, SetPlaybackRate) { |
| // Initialize Renderer because, as of this writing, the pipeline guarantees it |
| // will not call SetPlaybackRate() until after the media::Renderer is |
| // initialized. |
| InitializeRenderer(); |
| RunPendingTasks(); |
| ASSERT_EQ(0, ReceivedRpcMessageCount()); |
| |
| renderer_->SetPlaybackRate(2.5); |
| RunPendingTasks(); |
| ASSERT_EQ(1, ReceivedRpcMessageCount()); |
| // Checks if it sends out RPC message with correct value. |
| const openscreen::cast::RpcMessage* rpc = |
| PeekRpcMessage(openscreen::cast::RpcMessage::RPC_R_SETPLAYBACKRATE); |
| ASSERT_TRUE(rpc); |
| ASSERT_DOUBLE_EQ(2.5, rpc->double_value()); |
| } |
| |
| TEST_F(CourierRendererTest, OnTimeUpdate) { |
| base::TimeDelta media_time = base::Microseconds(100); |
| base::TimeDelta max_media_time = base::Microseconds(500); |
| IssueTimeUpdateRpc(media_time, max_media_time); |
| ValidateCurrentTime(media_time, max_media_time); |
| |
| // Issues RPC_RC_ONTIMEUPDATE RPC message with invalid time |
| base::TimeDelta media_time2 = base::Microseconds(-100); |
| base::TimeDelta max_media_time2 = base::Microseconds(500); |
| IssueTimeUpdateRpc(media_time2, max_media_time2); |
| // Because of invalid value, the time will not be updated and remain the same. |
| ValidateCurrentTime(media_time, max_media_time); |
| } |
| |
| TEST_F(CourierRendererTest, OnBufferingStateChange) { |
| InitializeRenderer(); |
| EXPECT_CALL(*render_client_, |
| OnBufferingStateChange(BUFFERING_HAVE_NOTHING, _)) |
| .Times(1); |
| IssuesBufferingStateRpc(BufferingState::BUFFERING_HAVE_NOTHING); |
| } |
| |
| TEST_F(CourierRendererTest, OnAudioConfigChange) { |
| const AudioDecoderConfig kNewAudioConfig( |
| AudioCodec::kVorbis, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 44100, |
| EmptyExtraData(), EncryptionScheme::kUnencrypted); |
| InitializeRenderer(); |
| // Make sure initial audio config does not match the one we intend to send. |
| ASSERT_FALSE(render_client_->audio_decoder_config().Matches(kNewAudioConfig)); |
| // Issues RPC_RC_ONVIDEOCONFIGCHANGE RPC message. |
| EXPECT_CALL(*render_client_, |
| OnAudioConfigChange(DecoderConfigEq(kNewAudioConfig))) |
| .Times(1); |
| |
| std::unique_ptr<openscreen::cast::RpcMessage> rpc( |
| new openscreen::cast::RpcMessage()); |
| rpc->set_handle(5); |
| rpc->set_proc(openscreen::cast::RpcMessage::RPC_RC_ONAUDIOCONFIGCHANGE); |
| auto* audio_config_change_message = |
| rpc->mutable_rendererclient_onaudioconfigchange_rpc(); |
| openscreen::cast::AudioDecoderConfig* proto_audio_config = |
| audio_config_change_message->mutable_audio_decoder_config(); |
| ConvertAudioDecoderConfigToProto(kNewAudioConfig, proto_audio_config); |
| OnReceivedRpc(std::move(rpc)); |
| RunPendingTasks(); |
| ASSERT_TRUE(render_client_->audio_decoder_config().Matches(kNewAudioConfig)); |
| } |
| |
| TEST_F(CourierRendererTest, OnVideoConfigChange) { |
| const auto kNewVideoConfig = TestVideoConfig::Normal(); |
| InitializeRenderer(); |
| // Make sure initial video config does not match the one we intend to send. |
| ASSERT_FALSE(render_client_->video_decoder_config().Matches(kNewVideoConfig)); |
| // Issues RPC_RC_ONVIDEOCONFIGCHANGE RPC message. |
| EXPECT_CALL(*render_client_, |
| OnVideoConfigChange(DecoderConfigEq(kNewVideoConfig))) |
| .Times(1); |
| |
| std::unique_ptr<openscreen::cast::RpcMessage> rpc( |
| new openscreen::cast::RpcMessage()); |
| rpc->set_handle(5); |
| rpc->set_proc(openscreen::cast::RpcMessage::RPC_RC_ONVIDEOCONFIGCHANGE); |
| auto* video_config_change_message = |
| rpc->mutable_rendererclient_onvideoconfigchange_rpc(); |
| openscreen::cast::VideoDecoderConfig* proto_video_config = |
| video_config_change_message->mutable_video_decoder_config(); |
| ConvertVideoDecoderConfigToProto(kNewVideoConfig, proto_video_config); |
| OnReceivedRpc(std::move(rpc)); |
| RunPendingTasks(); |
| ASSERT_TRUE(render_client_->video_decoder_config().Matches(kNewVideoConfig)); |
| } |
| |
| TEST_F(CourierRendererTest, OnVideoNaturalSizeChange) { |
| InitializeRenderer(); |
| // Makes sure initial value of video natural size is not set to |
| // gfx::Size(100, 200). |
| ASSERT_NE(render_client_->size().width(), 100); |
| ASSERT_NE(render_client_->size().height(), 200); |
| // Issues RPC_RC_ONVIDEONATURALSIZECHANGE RPC message. |
| EXPECT_CALL(*render_client_, OnVideoNaturalSizeChange(gfx::Size(100, 200))) |
| .Times(1); |
| std::unique_ptr<openscreen::cast::RpcMessage> rpc( |
| new openscreen::cast::RpcMessage()); |
| rpc->set_handle(5); |
| rpc->set_proc(openscreen::cast::RpcMessage::RPC_RC_ONVIDEONATURALSIZECHANGE); |
| auto* size_message = |
| rpc->mutable_rendererclient_onvideonatualsizechange_rpc(); |
| size_message->set_width(100); |
| size_message->set_height(200); |
| OnReceivedRpc(std::move(rpc)); |
| RunPendingTasks(); |
| ASSERT_EQ(render_client_->size().width(), 100); |
| ASSERT_EQ(render_client_->size().height(), 200); |
| } |
| |
| TEST_F(CourierRendererTest, OnVideoNaturalSizeChangeWithInvalidValue) { |
| InitializeRenderer(); |
| // Issues RPC_RC_ONVIDEONATURALSIZECHANGE RPC message. |
| EXPECT_CALL(*render_client_, OnVideoNaturalSizeChange(_)).Times(0); |
| std::unique_ptr<openscreen::cast::RpcMessage> rpc( |
| new openscreen::cast::RpcMessage()); |
| rpc->set_handle(5); |
| rpc->set_proc(openscreen::cast::RpcMessage::RPC_RC_ONVIDEONATURALSIZECHANGE); |
| auto* size_message = |
| rpc->mutable_rendererclient_onvideonatualsizechange_rpc(); |
| size_message->set_width(-100); |
| size_message->set_height(0); |
| OnReceivedRpc(std::move(rpc)); |
| RunPendingTasks(); |
| } |
| |
| TEST_F(CourierRendererTest, OnVideoOpacityChange) { |
| InitializeRenderer(); |
| ASSERT_FALSE(render_client_->opaque()); |
| // Issues RPC_RC_ONVIDEOOPACITYCHANGE RPC message. |
| EXPECT_CALL(*render_client_, OnVideoOpacityChange(true)).Times(1); |
| std::unique_ptr<openscreen::cast::RpcMessage> rpc( |
| new openscreen::cast::RpcMessage()); |
| rpc->set_handle(5); |
| rpc->set_proc(openscreen::cast::RpcMessage::RPC_RC_ONVIDEOOPACITYCHANGE); |
| rpc->set_boolean_value(true); |
| OnReceivedRpc(std::move(rpc)); |
| RunPendingTasks(); |
| ASSERT_TRUE(render_client_->opaque()); |
| } |
| |
| TEST_F(CourierRendererTest, OnStatisticsUpdate) { |
| InitializeRenderer(); |
| EXPECT_NE(DefaultStats(), render_client_->stats()); |
| IssueStatisticsUpdateRpc(); |
| EXPECT_EQ(DefaultStats(), render_client_->stats()); |
| } |
| |
| TEST_F(CourierRendererTest, OnPacingTooSlowly) { |
| InitializeRenderer(); |
| RewireSendMessageCallbackToSink(); |
| |
| // There should be no error reported with this playback rate. |
| renderer_->SetPlaybackRate(0.8); |
| RunPendingTasks(); |
| EXPECT_CALL(*render_client_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _)) |
| .Times(1); |
| IssuesBufferingStateRpc(BufferingState::BUFFERING_HAVE_ENOUGH); |
| clock_.Advance(base::Seconds(3)); |
| VerifyAndReportTimeUpdates(0, 15); |
| ASSERT_FALSE(DidEncounterFatalError()); |
| |
| // Change playback rate. Pacing keeps same as above. Should report error when |
| // playback was continuously delayed for 10 times. |
| renderer_->SetPlaybackRate(1); |
| RunPendingTasks(); |
| clock_.Advance(base::Seconds(3)); |
| VerifyAndReportTimeUpdates(15, 30); |
| ASSERT_TRUE(DidEncounterFatalError()); |
| } |
| |
| TEST_F(CourierRendererTest, OnFrameDropRateHigh) { |
| InitializeRenderer(); |
| |
| for (int i = 0; i < 7; ++i) { |
| ASSERT_FALSE(DidEncounterFatalError()); // Not enough measurements. |
| IssueStatisticsUpdateRpc(); |
| clock_.Advance(base::Seconds(1)); |
| RunPendingTasks(); |
| } |
| ASSERT_TRUE(DidEncounterFatalError()); |
| } |
| |
| } // namespace remoting |
| } // namespace media |