| // Copyright 2013 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 <math.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <functional> |
| #include <list> |
| #include <map> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/sys_byteorder.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/tick_clock.h" |
| #include "build/build_config.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/fake_single_thread_task_runner.h" |
| #include "media/base/video_frame.h" |
| #include "media/cast/cast_config.h" |
| #include "media/cast/cast_environment.h" |
| #include "media/cast/cast_sender.h" |
| #include "media/cast/logging/simple_event_subscriber.h" |
| #include "media/cast/net/cast_transport.h" |
| #include "media/cast/net/cast_transport_config.h" |
| #include "media/cast/net/cast_transport_defines.h" |
| #include "media/cast/net/cast_transport_impl.h" |
| #include "media/cast/test/receiver/cast_receiver.h" |
| #include "media/cast/test/skewed_single_thread_task_runner.h" |
| #include "media/cast/test/skewed_tick_clock.h" |
| #include "media/cast/test/utility/audio_utility.h" |
| #include "media/cast/test/utility/default_config.h" |
| #include "media/cast/test/utility/udp_proxy.h" |
| #include "media/cast/test/utility/video_utility.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace media { |
| namespace cast { |
| |
| namespace { |
| |
| // This test suite generates synthetic data. For audio, it's a sinusoid waveform |
| // with frequency kSoundFrequency and different amplitudes. For video, it's a |
| // pattern that is shifting by one pixel per frame. Each pixels' neighbors right |
| // and down is incremented by one; since the pixel value is 8 bit it will wrap |
| // frequently within the image. Visually, this creates diagonal color bands that |
| // move across the screen. |
| static const int64_t kStartMillisecond = INT64_C(1245); |
| static const int kAudioChannels = 2; |
| static const double kSoundFrequency = 314.15926535897; // Freq of sine wave. |
| static const float kSoundVolume = 0.5f; |
| static const int kVideoWidth = 320; |
| static const int kVideoHeight = 180; |
| |
| // Since the video encoded and decoded an error will be introduced; when |
| // comparing individual pixels the error can be quite large; we allow a PSNR of |
| // at least |kVideoAcceptedPSNR|. |
| static const double kVideoAcceptedPSNR = 38.0; |
| |
| // The tests are commonly implemented with |kFrameTimerMs| RunTask function; |
| // a normal video is 30 fps hence the 33 ms between frames. |
| // |
| // TODO(miu): The errors in timing will add up significantly. Find an |
| // alternative approach that eliminates use of this constant. |
| static const int kFrameTimerMs = 33; |
| |
| // The size of audio frames. The encoder joins/breaks all inserted audio into |
| // chunks of this size. |
| static const int kAudioFrameDurationMs = 10; |
| |
| // The amount of time between frame capture on the sender and playout on the |
| // receiver. |
| static const int kTargetPlayoutDelayMs = 100; |
| |
| // The maximum amount of deviation expected in the playout times emitted by the |
| // receiver. |
| static const int kMaxAllowedPlayoutErrorMs = 30; |
| |
| std::string ConvertFromBase16String(const std::string& base_16) { |
| std::string compressed; |
| DCHECK_EQ(base_16.size() % 2, 0u) << "Must be a multiple of 2"; |
| compressed.reserve(base_16.size() / 2); |
| |
| if (!base::HexStringToString(base_16, &compressed)) { |
| NOTREACHED(); |
| } |
| return compressed; |
| } |
| |
| void ExpectSuccessOperationalStatus(OperationalStatus status) { |
| EXPECT_EQ(STATUS_INITIALIZED, status); |
| } |
| |
| // This is wrapped in a struct because it needs to be put into a std::map. |
| typedef struct { |
| int counter[kNumOfLoggingEvents]; |
| } LoggingEventCounts; |
| |
| // Constructs a map from each frame (RTP timestamp) to counts of each event |
| // type logged for that frame. |
| std::map<RtpTimeTicks, LoggingEventCounts> GetEventCountsForFrameEvents( |
| const std::vector<FrameEvent>& frame_events) { |
| std::map<RtpTimeTicks, LoggingEventCounts> event_counter_for_frame; |
| for (const FrameEvent& frame_event : frame_events) { |
| auto map_it = event_counter_for_frame.find(frame_event.rtp_timestamp); |
| if (map_it == event_counter_for_frame.end()) { |
| LoggingEventCounts new_counter; |
| memset(&new_counter, 0, sizeof(new_counter)); |
| ++(new_counter.counter[frame_event.type]); |
| event_counter_for_frame.insert( |
| std::make_pair(frame_event.rtp_timestamp, new_counter)); |
| } else { |
| ++(map_it->second.counter[frame_event.type]); |
| } |
| } |
| return event_counter_for_frame; |
| } |
| |
| // Constructs a map from each packet (Packet ID) to counts of each event |
| // type logged for that packet. |
| std::map<uint16_t, LoggingEventCounts> GetEventCountsForPacketEvents( |
| const std::vector<PacketEvent>& packet_events) { |
| std::map<uint16_t, LoggingEventCounts> event_counter_for_packet; |
| for (const PacketEvent& packet_event : packet_events) { |
| auto map_it = event_counter_for_packet.find(packet_event.packet_id); |
| if (map_it == event_counter_for_packet.end()) { |
| LoggingEventCounts new_counter; |
| memset(&new_counter, 0, sizeof(new_counter)); |
| ++(new_counter.counter[packet_event.type]); |
| event_counter_for_packet.insert( |
| std::make_pair(packet_event.packet_id, new_counter)); |
| } else { |
| ++(map_it->second.counter[packet_event.type]); |
| } |
| } |
| return event_counter_for_packet; |
| } |
| |
| // Shim that turns forwards packets from a test::PacketPipe to a |
| // PacketReceiverCallback. |
| class LoopBackPacketPipe final : public test::PacketPipe { |
| public: |
| explicit LoopBackPacketPipe(const PacketReceiverCallback& packet_receiver) |
| : packet_receiver_(packet_receiver) {} |
| |
| ~LoopBackPacketPipe() override = default; |
| |
| // PacketPipe implementations. |
| void Send(std::unique_ptr<Packet> packet) override { |
| packet_receiver_.Run(std::move(packet)); |
| } |
| |
| private: |
| PacketReceiverCallback packet_receiver_; |
| }; |
| |
| // Class that sends the packet direct from sender into the receiver with the |
| // ability to drop packets between the two. |
| // |
| // TODO(miu): This should be reconciled/merged into |
| // media/cast/test/loopback_transport.*. It's roughly the same class and has |
| // exactly the same name (and when it was outside of the anonymous namespace bad |
| // things happened when linking on Android!). |
| class LoopBackTransport : public PacketTransport { |
| public: |
| explicit LoopBackTransport(scoped_refptr<CastEnvironment> cast_environment) |
| : send_packets_(true), |
| drop_packets_belonging_to_odd_frames_(false), |
| cast_environment_(cast_environment), |
| bytes_sent_(0) {} |
| |
| void SetPacketReceiver( |
| const PacketReceiverCallback& packet_receiver, |
| const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
| const base::TickClock* clock) { |
| std::unique_ptr<test::PacketPipe> loopback_pipe( |
| new LoopBackPacketPipe(packet_receiver)); |
| if (packet_pipe_) { |
| packet_pipe_->AppendToPipe(std::move(loopback_pipe)); |
| } else { |
| packet_pipe_ = std::move(loopback_pipe); |
| } |
| packet_pipe_->InitOnIOThread(task_runner, clock); |
| } |
| |
| bool SendPacket(PacketRef packet, base::OnceClosure cb) final { |
| DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); |
| if (!send_packets_) |
| return true; |
| |
| bytes_sent_ += packet->data.size(); |
| if (drop_packets_belonging_to_odd_frames_) { |
| const uint8_t truncated_frame_id = packet->data[13]; |
| if (truncated_frame_id % 2 == 1) |
| return true; |
| } |
| |
| std::unique_ptr<Packet> packet_copy(new Packet(packet->data)); |
| packet_pipe_->Send(std::move(packet_copy)); |
| return true; |
| } |
| |
| int64_t GetBytesSent() final { return bytes_sent_; } |
| |
| void StartReceiving(PacketReceiverCallbackWithStatus packet_receiver) final {} |
| |
| void StopReceiving() final {} |
| |
| void SetSendPackets(bool send_packets) { send_packets_ = send_packets; } |
| |
| void DropAllPacketsBelongingToOddFrames() { |
| drop_packets_belonging_to_odd_frames_ = true; |
| } |
| |
| void SetPacketPipe(std::unique_ptr<test::PacketPipe> pipe) { |
| // Append the loopback pipe to the end. |
| pipe->AppendToPipe(std::move(packet_pipe_)); |
| packet_pipe_ = std::move(pipe); |
| } |
| |
| private: |
| bool send_packets_; |
| bool drop_packets_belonging_to_odd_frames_; |
| scoped_refptr<CastEnvironment> cast_environment_; |
| std::unique_ptr<test::PacketPipe> packet_pipe_; |
| int64_t bytes_sent_; |
| }; |
| |
| // Class that verifies the audio frames coming out of the receiver. |
| class TestReceiverAudioCallback |
| : public base::RefCountedThreadSafe<TestReceiverAudioCallback> { |
| public: |
| struct ExpectedAudioFrame { |
| std::unique_ptr<AudioBus> audio_bus; |
| base::TimeTicks playout_time; |
| }; |
| |
| TestReceiverAudioCallback() : num_called_(0) {} |
| |
| void SetExpectedSamplingFrequency(int expected_sampling_frequency) { |
| expected_sampling_frequency_ = expected_sampling_frequency; |
| } |
| |
| void AddExpectedResult(const AudioBus& audio_bus, |
| base::TimeTicks playout_time) { |
| std::unique_ptr<ExpectedAudioFrame> expected_audio_frame( |
| new ExpectedAudioFrame()); |
| expected_audio_frame->audio_bus = |
| AudioBus::Create(audio_bus.channels(), audio_bus.frames()); |
| audio_bus.CopyTo(expected_audio_frame->audio_bus.get()); |
| expected_audio_frame->playout_time = playout_time; |
| expected_frames_.push_back(std::move(expected_audio_frame)); |
| } |
| |
| void IgnoreAudioFrame(std::unique_ptr<AudioBus> audio_bus, |
| base::TimeTicks playout_time, |
| bool is_continuous) { |
| ++num_called_; |
| } |
| |
| void CheckAudioFrame(std::unique_ptr<AudioBus> audio_bus, |
| base::TimeTicks playout_time, |
| bool is_continuous) { |
| ++num_called_; |
| |
| ASSERT_TRUE(audio_bus); |
| ASSERT_FALSE(expected_frames_.empty()); |
| const std::unique_ptr<ExpectedAudioFrame> expected_audio_frame = |
| std::move(expected_frames_.front()); |
| expected_frames_.pop_front(); |
| |
| EXPECT_EQ(audio_bus->channels(), kAudioChannels); |
| EXPECT_EQ(audio_bus->frames(), expected_audio_frame->audio_bus->frames()); |
| for (int ch = 0; ch < audio_bus->channels(); ++ch) { |
| EXPECT_NEAR( |
| CountZeroCrossings(expected_audio_frame->audio_bus->channel(ch), |
| expected_audio_frame->audio_bus->frames()), |
| CountZeroCrossings(audio_bus->channel(ch), audio_bus->frames()), 1); |
| } |
| |
| EXPECT_NEAR( |
| (playout_time - expected_audio_frame->playout_time).InMillisecondsF(), |
| 0.0, kMaxAllowedPlayoutErrorMs); |
| VLOG_IF(1, !last_playout_time_.is_null()) |
| << "Audio frame playout time delta (compared to last frame) is " |
| << (playout_time - last_playout_time_).InMicroseconds() << " usec."; |
| last_playout_time_ = playout_time; |
| |
| EXPECT_TRUE(is_continuous); |
| } |
| |
| int number_times_called() const { return num_called_; } |
| |
| protected: |
| virtual ~TestReceiverAudioCallback() = default; |
| |
| private: |
| friend class base::RefCountedThreadSafe<TestReceiverAudioCallback>; |
| |
| int num_called_; |
| int expected_sampling_frequency_; |
| std::list<std::unique_ptr<ExpectedAudioFrame>> expected_frames_; |
| base::TimeTicks last_playout_time_; |
| }; |
| |
| // Class that verifies the video frames coming out of the receiver. |
| class TestReceiverVideoCallback |
| : public base::RefCountedThreadSafe<TestReceiverVideoCallback> { |
| public: |
| struct ExpectedVideoFrame { |
| int frame_number; |
| gfx::Size size; |
| base::TimeTicks playout_time; |
| bool should_be_continuous; |
| }; |
| |
| TestReceiverVideoCallback() : num_called_(0) {} |
| |
| void AddExpectedResult(int frame_number, |
| const gfx::Size& size, |
| base::TimeTicks playout_time, |
| bool should_be_continuous) { |
| ExpectedVideoFrame expected_video_frame; |
| expected_video_frame.frame_number = frame_number; |
| expected_video_frame.size = size; |
| expected_video_frame.playout_time = playout_time; |
| expected_video_frame.should_be_continuous = should_be_continuous; |
| expected_frame_.push_back(expected_video_frame); |
| } |
| |
| void CheckVideoFrame(bool examine_content, |
| scoped_refptr<media::VideoFrame> video_frame, |
| base::TimeTicks playout_time, |
| bool is_continuous) { |
| ++num_called_; |
| |
| ASSERT_TRUE(video_frame); |
| ASSERT_FALSE(expected_frame_.empty()); |
| ExpectedVideoFrame expected_video_frame = expected_frame_.front(); |
| expected_frame_.pop_front(); |
| |
| EXPECT_EQ(expected_video_frame.size.width(), |
| video_frame->visible_rect().width()); |
| EXPECT_EQ(expected_video_frame.size.height(), |
| video_frame->visible_rect().height()); |
| |
| if (examine_content && expected_video_frame.should_be_continuous) { |
| scoped_refptr<media::VideoFrame> expected_I420_frame = |
| media::VideoFrame::CreateFrame( |
| PIXEL_FORMAT_I420, expected_video_frame.size, |
| gfx::Rect(expected_video_frame.size), expected_video_frame.size, |
| base::TimeDelta()); |
| PopulateVideoFrame(expected_I420_frame.get(), |
| expected_video_frame.frame_number); |
| EXPECT_LE(kVideoAcceptedPSNR, |
| I420PSNR(*expected_I420_frame, *video_frame)); |
| } |
| |
| EXPECT_NEAR( |
| (playout_time - expected_video_frame.playout_time).InMillisecondsF(), |
| 0.0, kMaxAllowedPlayoutErrorMs); |
| VLOG_IF(1, !last_playout_time_.is_null()) |
| << "Video frame playout time delta (compared to last frame) is " |
| << (playout_time - last_playout_time_).InMicroseconds() << " usec."; |
| last_playout_time_ = playout_time; |
| |
| EXPECT_EQ(expected_video_frame.should_be_continuous, is_continuous); |
| } |
| |
| int number_times_called() const { return num_called_; } |
| |
| protected: |
| virtual ~TestReceiverVideoCallback() = default; |
| |
| private: |
| friend class base::RefCountedThreadSafe<TestReceiverVideoCallback>; |
| |
| int num_called_; |
| std::list<ExpectedVideoFrame> expected_frame_; |
| base::TimeTicks last_playout_time_; |
| }; |
| |
| } // namespace |
| |
| // The actual test class, generate synthetic data for both audio and video and |
| // send those through the sender and receiver and analyzes the result. |
| class End2EndTest : public ::testing::Test { |
| public: |
| void ReceivePacket(std::unique_ptr<media::cast::Packet> packet) { |
| cast_receiver_->ReceivePacket(std::move(packet)); |
| } |
| |
| protected: |
| End2EndTest() |
| : start_time_(), |
| task_runner_(new FakeSingleThreadTaskRunner(&testing_clock_)), |
| testing_clock_sender_(&testing_clock_), |
| task_runner_sender_( |
| new test::SkewedSingleThreadTaskRunner(task_runner_)), |
| testing_clock_receiver_(&testing_clock_), |
| task_runner_receiver_( |
| new test::SkewedSingleThreadTaskRunner(task_runner_)), |
| cast_environment_sender_(new CastEnvironment(&testing_clock_sender_, |
| task_runner_sender_, |
| task_runner_sender_, |
| task_runner_sender_)), |
| cast_environment_receiver_(new CastEnvironment(&testing_clock_receiver_, |
| task_runner_receiver_, |
| task_runner_receiver_, |
| task_runner_receiver_)), |
| receiver_to_sender_(new LoopBackTransport(cast_environment_receiver_)), |
| sender_to_receiver_(new LoopBackTransport(cast_environment_sender_)), |
| test_receiver_audio_callback_(new TestReceiverAudioCallback()), |
| test_receiver_video_callback_(new TestReceiverVideoCallback()) { |
| testing_clock_.Advance(base::Milliseconds(kStartMillisecond)); |
| cast_environment_sender_->logger()->Subscribe(&event_subscriber_sender_); |
| } |
| |
| void Configure(Codec video_codec, Codec audio_codec) { |
| audio_sender_config_.sender_ssrc = 1; |
| audio_sender_config_.receiver_ssrc = 2; |
| audio_sender_config_.max_playout_delay = |
| base::Milliseconds(kTargetPlayoutDelayMs); |
| audio_sender_config_.rtp_payload_type = RtpPayloadType::AUDIO_OPUS; |
| audio_sender_config_.use_external_encoder = false; |
| audio_sender_config_.rtp_timebase = kDefaultAudioSamplingRate; |
| audio_sender_config_.channels = kAudioChannels; |
| audio_sender_config_.max_bitrate = kDefaultAudioEncoderBitrate; |
| audio_sender_config_.codec = audio_codec; |
| audio_sender_config_.aes_iv_mask = |
| ConvertFromBase16String("abcdeffedcba12345678900987654321"); |
| audio_sender_config_.aes_key = |
| ConvertFromBase16String("deadbeefcafecafedeadbeefb0b0b0b0"); |
| |
| audio_receiver_config_.receiver_ssrc = audio_sender_config_.receiver_ssrc; |
| audio_receiver_config_.sender_ssrc = audio_sender_config_.sender_ssrc; |
| audio_receiver_config_.rtp_max_delay_ms = kTargetPlayoutDelayMs; |
| audio_receiver_config_.rtp_payload_type = |
| audio_sender_config_.rtp_payload_type; |
| audio_receiver_config_.rtp_timebase = audio_sender_config_.rtp_timebase; |
| audio_receiver_config_.channels = kAudioChannels; |
| audio_receiver_config_.target_frame_rate = 100; |
| audio_receiver_config_.codec = audio_sender_config_.codec; |
| audio_receiver_config_.aes_iv_mask = audio_sender_config_.aes_iv_mask; |
| audio_receiver_config_.aes_key = audio_sender_config_.aes_key; |
| |
| test_receiver_audio_callback_->SetExpectedSamplingFrequency( |
| audio_receiver_config_.rtp_timebase); |
| |
| video_sender_config_.sender_ssrc = 3; |
| video_sender_config_.receiver_ssrc = 4; |
| video_sender_config_.max_playout_delay = |
| base::Milliseconds(kTargetPlayoutDelayMs); |
| video_sender_config_.rtp_payload_type = RtpPayloadType::VIDEO_VP8; |
| video_sender_config_.use_external_encoder = false; |
| video_sender_config_.rtp_timebase = kVideoFrequency; |
| video_sender_config_.max_bitrate = 50000; |
| video_sender_config_.min_bitrate = 10000; |
| video_sender_config_.start_bitrate = 10000; |
| video_sender_config_.video_codec_params.max_qp = 30; |
| video_sender_config_.video_codec_params.min_qp = 4; |
| video_sender_config_.max_frame_rate = 30; |
| video_sender_config_.codec = video_codec; |
| video_sender_config_.aes_iv_mask = |
| ConvertFromBase16String("1234567890abcdeffedcba0987654321"); |
| video_sender_config_.aes_key = |
| ConvertFromBase16String("deadbeefcafeb0b0b0b0cafedeadbeef"); |
| |
| video_receiver_config_.receiver_ssrc = video_sender_config_.receiver_ssrc; |
| video_receiver_config_.sender_ssrc = video_sender_config_.sender_ssrc; |
| video_receiver_config_.rtp_max_delay_ms = kTargetPlayoutDelayMs; |
| video_receiver_config_.rtp_payload_type = |
| video_sender_config_.rtp_payload_type; |
| video_receiver_config_.rtp_timebase = kVideoFrequency; |
| video_receiver_config_.channels = 1; |
| video_receiver_config_.target_frame_rate = |
| video_sender_config_.max_frame_rate; |
| video_receiver_config_.codec = video_sender_config_.codec; |
| video_receiver_config_.aes_iv_mask = video_sender_config_.aes_iv_mask; |
| video_receiver_config_.aes_key = video_sender_config_.aes_key; |
| } |
| |
| void SetReceiverSkew(double skew, base::TimeDelta offset) { |
| testing_clock_receiver_.SetSkew(skew, offset); |
| task_runner_receiver_->SetSkew(1.0 / skew); |
| } |
| |
| // Specify the minimum/maximum difference in playout times between two |
| // consecutive frames. Also, specify the maximum absolute rate of change over |
| // each three consecutive frames. |
| void SetExpectedVideoPlayoutSmoothness(base::TimeDelta min_delta, |
| base::TimeDelta max_delta, |
| base::TimeDelta max_curvature) { |
| min_video_playout_delta_ = min_delta; |
| max_video_playout_delta_ = max_delta; |
| max_video_playout_curvature_ = max_curvature; |
| } |
| |
| void FeedAudioFrames(int count, bool will_be_checked) { |
| for (int i = 0; i < count; ++i) { |
| std::unique_ptr<AudioBus> audio_bus(audio_bus_factory_->NextAudioBus( |
| base::Milliseconds(kAudioFrameDurationMs))); |
| const base::TimeTicks reference_time = |
| testing_clock_sender_.NowTicks() + |
| i * base::Milliseconds(kAudioFrameDurationMs); |
| if (will_be_checked) { |
| test_receiver_audio_callback_->AddExpectedResult( |
| *audio_bus, |
| reference_time + base::Milliseconds(kTargetPlayoutDelayMs)); |
| } |
| audio_frame_input_->InsertAudio(std::move(audio_bus), reference_time); |
| } |
| } |
| |
| void FeedAudioFramesWithExpectedDelay(int count, base::TimeDelta delay) { |
| for (int i = 0; i < count; ++i) { |
| std::unique_ptr<AudioBus> audio_bus(audio_bus_factory_->NextAudioBus( |
| base::Milliseconds(kAudioFrameDurationMs))); |
| const base::TimeTicks reference_time = |
| testing_clock_sender_.NowTicks() + |
| i * base::Milliseconds(kAudioFrameDurationMs); |
| test_receiver_audio_callback_->AddExpectedResult( |
| *audio_bus, |
| reference_time + delay + base::Milliseconds(kTargetPlayoutDelayMs)); |
| audio_frame_input_->InsertAudio(std::move(audio_bus), reference_time); |
| } |
| } |
| |
| void RequestAudioFrames(int count, bool with_check) { |
| for (int i = 0; i < count; ++i) { |
| cast_receiver_->RequestDecodedAudioFrame(base::BindRepeating( |
| with_check ? &TestReceiverAudioCallback::CheckAudioFrame |
| : &TestReceiverAudioCallback::IgnoreAudioFrame, |
| test_receiver_audio_callback_)); |
| } |
| } |
| |
| void Create(); |
| |
| ~End2EndTest() override { |
| cast_environment_sender_->logger()->Unsubscribe(&event_subscriber_sender_); |
| } |
| |
| void TearDown() final { |
| cast_sender_.reset(); |
| cast_receiver_.reset(); |
| task_runner_->RunTasks(); |
| } |
| |
| gfx::Size GetTestVideoFrameSize() const { |
| if (video_sender_config_.codec == CODEC_VIDEO_FAKE) |
| return gfx::Size(2, 2); |
| else |
| return gfx::Size(kVideoWidth, kVideoHeight); |
| } |
| |
| void SendVideoFrame(int frame_number, base::TimeTicks reference_time) { |
| if (start_time_.is_null()) |
| start_time_ = reference_time; |
| const base::TimeDelta time_diff = reference_time - start_time_; |
| scoped_refptr<media::VideoFrame> video_frame; |
| if (video_sender_config_.codec == CODEC_VIDEO_FAKE) { |
| video_frame = |
| media::VideoFrame::CreateBlackFrame(GetTestVideoFrameSize()); |
| } else { |
| const gfx::Size size = GetTestVideoFrameSize(); |
| video_frame = media::VideoFrame::CreateFrame( |
| PIXEL_FORMAT_I420, size, gfx::Rect(size), size, time_diff); |
| PopulateVideoFrame(video_frame.get(), frame_number); |
| } |
| video_frame->set_timestamp(reference_time - start_time_); |
| video_frame_input_->InsertRawVideoFrame(video_frame, reference_time); |
| } |
| |
| void RunTasks(int ms) { task_runner_->Sleep(base::Milliseconds(ms)); } |
| |
| // Send and receive audio and video frames for the given |duration|. Returns |
| // the total number of audio and video frames sent. |
| std::pair<int, int> RunAudioVideoLoop(base::TimeDelta duration) { |
| base::TimeTicks next_video_frame_at = testing_clock_.NowTicks(); |
| base::TimeTicks video_reference_time; |
| int audio_frames_sent = 0; |
| int video_frames_sent = 0; |
| const base::TimeTicks end_time = testing_clock_.NowTicks() + duration; |
| while (testing_clock_.NowTicks() < end_time) { |
| // Opus introduces a tiny delay before the sinewave starts; so don't |
| // examine the first audio frame's data receiver-side. |
| const bool verify_audio_data = |
| audio_frames_sent > 0 || |
| audio_sender_config_.codec == CODEC_AUDIO_PCM16; |
| FeedAudioFrames(1, verify_audio_data); |
| ++audio_frames_sent; |
| |
| const bool send_and_receive_a_video_frame = |
| testing_clock_.NowTicks() >= next_video_frame_at; |
| if (send_and_receive_a_video_frame) { |
| video_reference_time = next_video_frame_at; |
| next_video_frame_at += base::Milliseconds(kFrameTimerMs); |
| test_receiver_video_callback_->AddExpectedResult( |
| video_frames_sent, GetTestVideoFrameSize(), |
| testing_clock_.NowTicks() + |
| base::Milliseconds(kTargetPlayoutDelayMs), |
| true); |
| SendVideoFrame(video_frames_sent, video_reference_time); |
| ++video_frames_sent; |
| } |
| |
| RunTasks(kAudioFrameDurationMs); |
| |
| RequestAudioFrames(1, verify_audio_data); |
| if (send_and_receive_a_video_frame) { |
| cast_receiver_->RequestDecodedVideoFrame(base::BindRepeating( |
| &TestReceiverVideoCallback::CheckVideoFrame, |
| test_receiver_video_callback_, |
| video_sender_config_.codec != CODEC_VIDEO_FAKE)); |
| } |
| } |
| |
| // Verify all audio and video frames were received. |
| RunTasks(kFrameTimerMs + kTargetPlayoutDelayMs); // Let the data flow. |
| EXPECT_EQ(audio_frames_sent, |
| test_receiver_audio_callback_->number_times_called()); |
| EXPECT_EQ(video_frames_sent, |
| test_receiver_video_callback_->number_times_called()); |
| |
| return std::make_pair(audio_frames_sent, video_frames_sent); |
| } |
| |
| // Queries the EventSubscriber for all accumulated frame and packet events for |
| // audio and video and verifies all logging information was captured |
| // correctly. |
| void VerifyLogging(int num_expected_audio_frames, |
| int num_expected_video_frames) { |
| // Partition the frame and packet events into separate vectors for audio |
| // versus video. |
| std::vector<FrameEvent> all_frame_events; |
| event_subscriber_sender_.GetFrameEventsAndReset(&all_frame_events); |
| std::vector<FrameEvent> audio_frame_events; |
| std::vector<FrameEvent> video_frame_events; |
| for (const FrameEvent& event : all_frame_events) { |
| switch (event.media_type) { |
| case AUDIO_EVENT: |
| audio_frame_events.push_back(event); |
| break; |
| case VIDEO_EVENT: |
| video_frame_events.push_back(event); |
| break; |
| default: |
| FAIL(); |
| } |
| } |
| std::vector<PacketEvent> all_packet_events; |
| event_subscriber_sender_.GetPacketEventsAndReset(&all_packet_events); |
| std::vector<PacketEvent> audio_packet_events; |
| std::vector<PacketEvent> video_packet_events; |
| for (const PacketEvent& event : all_packet_events) { |
| switch (event.media_type) { |
| case AUDIO_EVENT: |
| audio_packet_events.push_back(event); |
| break; |
| case VIDEO_EVENT: |
| video_packet_events.push_back(event); |
| break; |
| default: |
| FAIL(); |
| } |
| } |
| |
| // For each frame, count the number of events that occurred for each event |
| // for that frame. |
| std::map<RtpTimeTicks, LoggingEventCounts> audio_event_counts_by_frame = |
| GetEventCountsForFrameEvents(audio_frame_events); |
| EXPECT_EQ(static_cast<size_t>(num_expected_audio_frames), |
| audio_event_counts_by_frame.size()); |
| std::map<RtpTimeTicks, LoggingEventCounts> video_event_counts_by_frame = |
| GetEventCountsForFrameEvents(video_frame_events); |
| EXPECT_EQ(static_cast<size_t>(num_expected_video_frames), |
| video_event_counts_by_frame.size()); |
| |
| // Examine the types of each frame and packet event and verify required |
| // events are present and unknown ones are not. |
| VerifyLoggingEventCounts(audio_event_counts_by_frame, |
| GetEventCountsForPacketEvents(audio_packet_events), |
| true); |
| VerifyLoggingEventCounts(video_event_counts_by_frame, |
| GetEventCountsForPacketEvents(video_packet_events), |
| false); |
| } |
| |
| // Examines histograms of event types to verify all logging information was |
| // captured correctly. |
| static void VerifyLoggingEventCounts( |
| const std::map<RtpTimeTicks, LoggingEventCounts>& event_counts_by_frame, |
| const std::map<uint16_t, LoggingEventCounts>& event_counts_by_packet, |
| bool for_audio) { |
| // Verify that each frame has the expected types of events logged. |
| for (const auto& e : event_counts_by_frame) { |
| int total_event_count_for_frame = 0; |
| for (int i = 0; i < kNumOfLoggingEvents; ++i) { |
| total_event_count_for_frame += e.second.counter[i]; |
| } |
| |
| int count_of_valid_events = 0; |
| if (!for_audio) { |
| EXPECT_EQ(1, e.second.counter[FRAME_CAPTURE_BEGIN]); |
| ++count_of_valid_events; |
| EXPECT_EQ(1, e.second.counter[FRAME_CAPTURE_END]); |
| ++count_of_valid_events; |
| } |
| EXPECT_EQ(1, e.second.counter[FRAME_ENCODED]); |
| ++count_of_valid_events; |
| EXPECT_EQ(1, e.second.counter[FRAME_DECODED]); |
| ++count_of_valid_events; |
| EXPECT_EQ(1, e.second.counter[FRAME_PLAYOUT]); |
| ++count_of_valid_events; |
| |
| // There is no guarantee that FRAME_ACK_SENT is logged exactly once per |
| // frame. |
| EXPECT_GT(e.second.counter[FRAME_ACK_SENT], 0); |
| count_of_valid_events += e.second.counter[FRAME_ACK_SENT]; |
| |
| // There is no guarantee that FRAME_ACK_RECEIVED is logged exactly once |
| // per frame. |
| EXPECT_GT(e.second.counter[FRAME_ACK_RECEIVED], 0); |
| count_of_valid_events += e.second.counter[FRAME_ACK_RECEIVED]; |
| |
| // Verify that there were no unexpected events logged with respect to this |
| // frame. |
| EXPECT_EQ(count_of_valid_events, total_event_count_for_frame); |
| } |
| |
| // Verify that each packet has the expected types of events logged. |
| for (const auto& e : event_counts_by_packet) { |
| int total_event_count_for_packet = 0; |
| for (int i = 0; i < kNumOfLoggingEvents; ++i) { |
| total_event_count_for_packet += e.second.counter[i]; |
| } |
| |
| EXPECT_GT(e.second.counter[PACKET_RECEIVED], 0); |
| const int packets_received = e.second.counter[PACKET_RECEIVED]; |
| const int packets_sent = e.second.counter[PACKET_SENT_TO_NETWORK]; |
| EXPECT_EQ(packets_sent, packets_received); |
| |
| // Verify that there were no other events logged with respect to this |
| // packet. An assumption here is that there was no packet loss nor |
| // retransmits during the end-to-end run. |
| EXPECT_EQ(packets_received + packets_sent, total_event_count_for_packet); |
| } |
| } |
| |
| void BasicPlayerGotVideoFrame(scoped_refptr<media::VideoFrame> video_frame, |
| base::TimeTicks playout_time, |
| bool continuous) { |
| // The following tests that the sender and receiver clocks can be |
| // out-of-sync, drift, and jitter with respect to one another; and depsite |
| // this, the receiver will produce smoothly-progressing playout times. |
| // Both first-order and second-order effects are tested. |
| if (!last_video_playout_time_.is_null() && |
| min_video_playout_delta_ > base::TimeDelta()) { |
| const base::TimeDelta delta = playout_time - last_video_playout_time_; |
| VLOG(1) << "Video frame playout time delta (compared to last frame) is " |
| << delta.InMicroseconds() << " usec."; |
| EXPECT_LE(min_video_playout_delta_.InMicroseconds(), |
| delta.InMicroseconds()); |
| EXPECT_GE(max_video_playout_delta_.InMicroseconds(), |
| delta.InMicroseconds()); |
| if (last_video_playout_delta_ > base::TimeDelta()) { |
| base::TimeDelta abs_curvature = delta - last_video_playout_delta_; |
| if (abs_curvature < base::TimeDelta()) |
| abs_curvature = -abs_curvature; |
| EXPECT_GE(max_video_playout_curvature_.InMicroseconds(), |
| abs_curvature.InMicroseconds()); |
| } |
| last_video_playout_delta_ = delta; |
| } |
| last_video_playout_time_ = playout_time; |
| |
| video_ticks_.push_back( |
| std::make_pair(testing_clock_receiver_.NowTicks(), playout_time)); |
| cast_receiver_->RequestDecodedVideoFrame(base::BindRepeating( |
| &End2EndTest::BasicPlayerGotVideoFrame, base::Unretained(this))); |
| } |
| |
| void BasicPlayerGotAudioFrame(std::unique_ptr<AudioBus> audio_bus, |
| base::TimeTicks playout_time, |
| bool is_continuous) { |
| VLOG_IF(1, !last_audio_playout_time_.is_null()) |
| << "Audio frame playout time delta (compared to last frame) is " |
| << (playout_time - last_audio_playout_time_).InMicroseconds() |
| << " usec."; |
| last_audio_playout_time_ = playout_time; |
| |
| audio_ticks_.push_back( |
| std::make_pair(testing_clock_receiver_.NowTicks(), playout_time)); |
| cast_receiver_->RequestDecodedAudioFrame(base::BindRepeating( |
| &End2EndTest::BasicPlayerGotAudioFrame, base::Unretained(this))); |
| } |
| |
| void StartBasicPlayer() { |
| cast_receiver_->RequestDecodedVideoFrame(base::BindRepeating( |
| &End2EndTest::BasicPlayerGotVideoFrame, base::Unretained(this))); |
| cast_receiver_->RequestDecodedAudioFrame(base::BindRepeating( |
| &End2EndTest::BasicPlayerGotAudioFrame, base::Unretained(this))); |
| } |
| |
| FrameReceiverConfig audio_receiver_config_; |
| FrameReceiverConfig video_receiver_config_; |
| FrameSenderConfig audio_sender_config_; |
| FrameSenderConfig video_sender_config_; |
| |
| base::TimeTicks start_time_; |
| |
| // These run in "test time" |
| base::SimpleTestTickClock testing_clock_; |
| scoped_refptr<FakeSingleThreadTaskRunner> task_runner_; |
| |
| // These run on the sender timeline. |
| test::SkewedTickClock testing_clock_sender_; |
| scoped_refptr<test::SkewedSingleThreadTaskRunner> task_runner_sender_; |
| |
| // These run on the receiver timeline. |
| test::SkewedTickClock testing_clock_receiver_; |
| scoped_refptr<test::SkewedSingleThreadTaskRunner> task_runner_receiver_; |
| base::TimeDelta min_video_playout_delta_; |
| base::TimeDelta max_video_playout_delta_; |
| base::TimeDelta max_video_playout_curvature_; |
| base::TimeTicks last_video_playout_time_; |
| base::TimeDelta last_video_playout_delta_; |
| base::TimeTicks last_audio_playout_time_; |
| |
| scoped_refptr<CastEnvironment> cast_environment_sender_; |
| scoped_refptr<CastEnvironment> cast_environment_receiver_; |
| |
| LoopBackTransport* receiver_to_sender_; // Owned by CastTransport. |
| LoopBackTransport* sender_to_receiver_; // Owned by CastTransport. |
| |
| std::unique_ptr<CastTransportImpl> transport_sender_; |
| std::unique_ptr<CastTransportImpl> transport_receiver_; |
| |
| std::unique_ptr<CastReceiver> cast_receiver_; |
| std::unique_ptr<CastSender> cast_sender_; |
| scoped_refptr<AudioFrameInput> audio_frame_input_; |
| scoped_refptr<VideoFrameInput> video_frame_input_; |
| |
| scoped_refptr<TestReceiverAudioCallback> test_receiver_audio_callback_; |
| scoped_refptr<TestReceiverVideoCallback> test_receiver_video_callback_; |
| |
| std::unique_ptr<TestAudioBusFactory> audio_bus_factory_; |
| |
| SimpleEventSubscriber event_subscriber_sender_; |
| |
| std::vector<std::pair<base::TimeTicks, base::TimeTicks>> audio_ticks_; |
| std::vector<std::pair<base::TimeTicks, base::TimeTicks>> video_ticks_; |
| |
| // |transport_sender_| has a RepeatingTimer which needs a MessageLoop. |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| }; |
| |
| namespace { |
| |
| class TransportClient : public CastTransport::Client { |
| public: |
| TransportClient(LogEventDispatcher* log_event_dispatcher, |
| End2EndTest* e2e_test) |
| : log_event_dispatcher_(log_event_dispatcher), e2e_test_(e2e_test) {} |
| |
| void OnStatusChanged(media::cast::CastTransportStatus status) final { |
| EXPECT_EQ(TRANSPORT_STREAM_INITIALIZED, status); |
| } |
| void OnLoggingEventsReceived( |
| std::unique_ptr<std::vector<FrameEvent>> frame_events, |
| std::unique_ptr<std::vector<PacketEvent>> packet_events) final { |
| log_event_dispatcher_->DispatchBatchOfEvents(std::move(frame_events), |
| std::move(packet_events)); |
| } |
| void ProcessRtpPacket(std::unique_ptr<Packet> packet) final { |
| if (e2e_test_) |
| e2e_test_->ReceivePacket(std::move(packet)); |
| } |
| |
| private: |
| LogEventDispatcher* const log_event_dispatcher_; // Not owned by this class. |
| End2EndTest* const e2e_test_; // Not owned by this class. |
| |
| DISALLOW_COPY_AND_ASSIGN(TransportClient); |
| }; |
| |
| // Cast E2E tests are not designed to run on official builds, primarily due |
| // to using fake codecs that cause official failures. |
| #ifdef OFFICIAL_BUILD |
| #define CAST_E2E_TEST(name) DISABLED_##name |
| #else |
| #define CAST_E2E_TEST(name) name |
| #endif |
| |
| } // namespace |
| |
| void End2EndTest::Create() { |
| transport_sender_ = std::make_unique<CastTransportImpl>( |
| &testing_clock_sender_, base::Milliseconds(1), |
| std::make_unique<TransportClient>(cast_environment_sender_->logger(), |
| nullptr), |
| base::WrapUnique(sender_to_receiver_), task_runner_sender_); |
| |
| transport_receiver_ = std::make_unique<CastTransportImpl>( |
| &testing_clock_sender_, base::Milliseconds(1), |
| std::make_unique<TransportClient>(cast_environment_receiver_->logger(), |
| this), |
| base::WrapUnique(receiver_to_sender_), task_runner_sender_); |
| |
| cast_receiver_ = |
| CastReceiver::Create(cast_environment_receiver_, audio_receiver_config_, |
| video_receiver_config_, transport_receiver_.get()); |
| |
| cast_sender_ = |
| CastSender::Create(cast_environment_sender_, transport_sender_.get()); |
| |
| // Initializing audio and video senders. |
| cast_sender_->InitializeAudio( |
| audio_sender_config_, base::BindOnce(&ExpectSuccessOperationalStatus)); |
| cast_sender_->InitializeVideo( |
| video_sender_config_, |
| base::BindRepeating(&ExpectSuccessOperationalStatus), base::DoNothing()); |
| task_runner_->RunTasks(); |
| |
| receiver_to_sender_->SetPacketReceiver( |
| transport_sender_->PacketReceiverForTesting(), task_runner_, |
| &testing_clock_); |
| sender_to_receiver_->SetPacketReceiver( |
| transport_receiver_->PacketReceiverForTesting(), task_runner_, |
| &testing_clock_); |
| |
| audio_frame_input_ = cast_sender_->audio_frame_input(); |
| video_frame_input_ = cast_sender_->video_frame_input(); |
| |
| audio_bus_factory_ = std::make_unique<TestAudioBusFactory>( |
| audio_sender_config_.channels, audio_sender_config_.rtp_timebase, |
| kSoundFrequency, kSoundVolume); |
| } |
| |
| TEST_F(End2EndTest, CAST_E2E_TEST(LoopWithLosslessEncoding)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| Create(); |
| |
| const auto frames_sent = RunAudioVideoLoop(base::Seconds(3)); |
| |
| // Make sure that we send a RTCP message containing receiver log data, then |
| // verify the accumulated logging data. |
| RunTasks(750); |
| VerifyLogging(frames_sent.first, frames_sent.second); |
| } |
| |
| TEST_F(End2EndTest, LoopWithLossyEncoding) { |
| Configure(CODEC_VIDEO_VP8, CODEC_AUDIO_OPUS); |
| Create(); |
| |
| const auto frames_sent = RunAudioVideoLoop(base::Seconds(1)); |
| |
| // Run tasks for 750 ms to ensure RTCP messages containing log data from the |
| // receiver are sent and processed by the sender. Then, verify the expected |
| // logging data is present. |
| RunTasks(750); |
| VerifyLogging(frames_sent.first, frames_sent.second); |
| } |
| |
| // This tests start sending audio and video at start-up time before the receiver |
| // is ready; it sends 2 frames before the receiver comes online. |
| // |
| // Test disabled due to flakiness: It appears that the RTCP synchronization |
| // sometimes kicks in, and sometimes doesn't. When it does, there's a sharp |
| // discontinuity in the timeline, throwing off the test expectations. See TODOs |
| // in audio_receiver.cc for likely cause(s) of this bug. |
| // http://crbug.com/573126 (history: http://crbug.com/314233) |
| TEST_F(End2EndTest, DISABLED_StartSenderBeforeReceiver) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| Create(); |
| |
| int frame_number = 0; |
| int audio_diff = kFrameTimerMs; |
| |
| sender_to_receiver_->SetSendPackets(false); |
| |
| const int test_delay_ms = 100; |
| |
| const int kNumVideoFramesBeforeReceiverStarted = 2; |
| const base::TimeTicks initial_send_time = testing_clock_sender_.NowTicks(); |
| const base::TimeDelta expected_delay = |
| base::Milliseconds(test_delay_ms + kFrameTimerMs); |
| for (int i = 0; i < kNumVideoFramesBeforeReceiverStarted; ++i) { |
| const int num_audio_frames = audio_diff / kAudioFrameDurationMs; |
| audio_diff -= num_audio_frames * kAudioFrameDurationMs; |
| |
| if (num_audio_frames > 0) |
| FeedAudioFramesWithExpectedDelay(1, expected_delay); |
| |
| // Frame will be rendered with 100mS delay, as the transmission is delayed. |
| // The receiver at this point cannot be synced to the sender's clock, as no |
| // packets, and specifically no RTCP packets were sent. |
| test_receiver_video_callback_->AddExpectedResult( |
| frame_number, GetTestVideoFrameSize(), |
| initial_send_time + expected_delay + |
| base::Milliseconds(kTargetPlayoutDelayMs), |
| true); |
| SendVideoFrame(frame_number++, testing_clock_sender_.NowTicks()); |
| |
| if (num_audio_frames > 0) |
| RunTasks(kAudioFrameDurationMs); // Advance clock forward. |
| if (num_audio_frames > 1) |
| FeedAudioFramesWithExpectedDelay(num_audio_frames - 1, expected_delay); |
| |
| RunTasks(kFrameTimerMs - kAudioFrameDurationMs); |
| audio_diff += kFrameTimerMs; |
| } |
| |
| RunTasks(test_delay_ms); |
| sender_to_receiver_->SetSendPackets(true); |
| |
| int num_audio_frames_requested = 0; |
| for (int j = 0; j < 10; ++j) { |
| const int num_audio_frames = audio_diff / kAudioFrameDurationMs; |
| audio_diff -= num_audio_frames * kAudioFrameDurationMs; |
| |
| if (num_audio_frames > 0) |
| FeedAudioFrames(1, true); |
| |
| test_receiver_video_callback_->AddExpectedResult( |
| frame_number, GetTestVideoFrameSize(), |
| testing_clock_sender_.NowTicks() + |
| base::Milliseconds(kTargetPlayoutDelayMs), |
| true); |
| SendVideoFrame(frame_number++, testing_clock_sender_.NowTicks()); |
| |
| if (num_audio_frames > 0) |
| RunTasks(kAudioFrameDurationMs); // Advance clock forward. |
| if (num_audio_frames > 1) |
| FeedAudioFrames(num_audio_frames - 1, true); |
| |
| RequestAudioFrames(num_audio_frames, true); |
| num_audio_frames_requested += num_audio_frames; |
| |
| cast_receiver_->RequestDecodedVideoFrame( |
| base::BindRepeating(&TestReceiverVideoCallback::CheckVideoFrame, |
| test_receiver_video_callback_, |
| video_sender_config_.codec != CODEC_VIDEO_FAKE)); |
| |
| RunTasks(kFrameTimerMs - kAudioFrameDurationMs); |
| audio_diff += kFrameTimerMs; |
| } |
| RunTasks(2 * kFrameTimerMs + 1); // Empty the receiver pipeline. |
| EXPECT_EQ(num_audio_frames_requested, |
| test_receiver_audio_callback_->number_times_called()); |
| EXPECT_EQ(10, test_receiver_video_callback_->number_times_called()); |
| } |
| |
| TEST_F(End2EndTest, CAST_E2E_TEST(BasicFakeSoftwareVideo)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| Create(); |
| StartBasicPlayer(); |
| SetReceiverSkew(1.0, base::Milliseconds(1)); |
| |
| // Expect very smooth playout when there is no clock skew. |
| SetExpectedVideoPlayoutSmoothness( |
| base::Milliseconds(kFrameTimerMs) * 99 / 100, |
| base::Milliseconds(kFrameTimerMs) * 101 / 100, |
| base::Milliseconds(kFrameTimerMs) / 100); |
| |
| int frames_counter = 0; |
| for (; frames_counter < 30; ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(kFrameTimerMs); |
| } |
| RunTasks(2 * kFrameTimerMs + 1); // Empty the pipeline. |
| EXPECT_EQ(30ul, video_ticks_.size()); |
| } |
| |
| // The following tests run many many iterations to make sure that buffers don't |
| // fill, timers don't go askew etc. However, these high-level tests are too |
| // expensive when running under sanitizers, or in non-optimized debug builds. |
| // In these cases, we reduce the number of iterations. |
| #if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || \ |
| defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \ |
| defined(UNDEFINED_SANITIZER) |
| const int kLongTestIterations = 500; // http://crbug.com/487033 |
| #elif defined(NDEBUG) |
| const int kLongTestIterations = 10000; |
| #else |
| const int kLongTestIterations = 1000; |
| #endif |
| |
| TEST_F(End2EndTest, CAST_E2E_TEST(ReceiverClockFast)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| Create(); |
| StartBasicPlayer(); |
| SetReceiverSkew(2.0, base::Microseconds(1234567)); |
| |
| for (int frames_counter = 0; frames_counter < kLongTestIterations; |
| ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(kFrameTimerMs); |
| } |
| RunTasks(2 * kFrameTimerMs + 1); // Empty the pipeline. |
| EXPECT_EQ(static_cast<size_t>(kLongTestIterations), video_ticks_.size()); |
| } |
| |
| TEST_F(End2EndTest, CAST_E2E_TEST(ReceiverClockSlow)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| Create(); |
| StartBasicPlayer(); |
| SetReceiverSkew(0.5, base::Microseconds(-765432)); |
| |
| for (int frames_counter = 0; frames_counter < kLongTestIterations; |
| ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(kFrameTimerMs); |
| } |
| RunTasks(2 * kFrameTimerMs + 1); // Empty the pipeline. |
| EXPECT_EQ(static_cast<size_t>(kLongTestIterations), video_ticks_.size()); |
| } |
| |
| TEST_F(End2EndTest, CAST_E2E_TEST(SmoothPlayoutWithFivePercentClockRateSkew)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| Create(); |
| StartBasicPlayer(); |
| SetReceiverSkew(1.05, base::Milliseconds(-42)); |
| |
| // Expect smooth playout when there is 5% skew. |
| SetExpectedVideoPlayoutSmoothness( |
| base::Milliseconds(kFrameTimerMs) * 90 / 100, |
| base::Milliseconds(kFrameTimerMs) * 110 / 100, |
| base::Milliseconds(kFrameTimerMs) / 10); |
| |
| for (int frames_counter = 0; frames_counter < kLongTestIterations; |
| ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(kFrameTimerMs); |
| } |
| RunTasks(2 * kFrameTimerMs + 1); // Empty the pipeline. |
| EXPECT_EQ(static_cast<size_t>(kLongTestIterations), video_ticks_.size()); |
| } |
| |
| TEST_F(End2EndTest, CAST_E2E_TEST(EvilNetwork)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| receiver_to_sender_->SetPacketPipe(test::EvilNetwork()); |
| sender_to_receiver_->SetPacketPipe(test::EvilNetwork()); |
| Create(); |
| StartBasicPlayer(); |
| |
| for (int frames_counter = 0; frames_counter < kLongTestIterations; |
| ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(kFrameTimerMs); |
| } |
| base::TimeTicks test_end = testing_clock_receiver_.NowTicks(); |
| RunTasks(100 * kFrameTimerMs + 1); // Empty the pipeline. |
| EXPECT_LT(static_cast<size_t>(kLongTestIterations / 100), |
| video_ticks_.size()); |
| VLOG(1) << "Fully transmitted " << video_ticks_.size() << " frames."; |
| EXPECT_GT(1000, (video_ticks_.back().second - test_end).InMilliseconds()); |
| } |
| |
| // Tests that a system configured for 30 FPS drops frames when input is provided |
| // at a much higher frame rate. |
| TEST_F(End2EndTest, CAST_E2E_TEST(ShoveHighFrameRateDownYerThroat)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| receiver_to_sender_->SetPacketPipe(test::EvilNetwork()); |
| sender_to_receiver_->SetPacketPipe(test::EvilNetwork()); |
| Create(); |
| StartBasicPlayer(); |
| |
| for (int frames_counter = 0; frames_counter < kLongTestIterations; |
| ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(10 /* 10 ms, but 33.3 expected by system */); |
| } |
| base::TimeTicks test_end = testing_clock_receiver_.NowTicks(); |
| RunTasks(100 * kFrameTimerMs + 1); // Empty the pipeline. |
| EXPECT_LT(static_cast<size_t>(kLongTestIterations / 100), |
| video_ticks_.size()); |
| |
| #if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || \ |
| defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \ |
| defined(UNDEFINED_SANITIZER) |
| // We want to ensure that at least one frame is dropped on ASAN builds. |
| const size_t max_iterations = kLongTestIterations - 1; |
| #else |
| const size_t max_iterations = kLongTestIterations / 3; |
| #endif |
| EXPECT_GE(max_iterations, video_ticks_.size()); |
| |
| VLOG(1) << "Fully transmitted " << video_ticks_.size() << " frames."; |
| EXPECT_LT((video_ticks_.back().second - test_end).InMilliseconds(), 1000); |
| } |
| |
| TEST_F(End2EndTest, CAST_E2E_TEST(OldPacketNetwork)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| sender_to_receiver_->SetPacketPipe(test::NewRandomDrop(0.01)); |
| std::unique_ptr<test::PacketPipe> echo_chamber( |
| test::NewDuplicateAndDelay(1, 10 * kFrameTimerMs)); |
| echo_chamber->AppendToPipe(test::NewDuplicateAndDelay(1, 20 * kFrameTimerMs)); |
| echo_chamber->AppendToPipe(test::NewDuplicateAndDelay(1, 40 * kFrameTimerMs)); |
| echo_chamber->AppendToPipe(test::NewDuplicateAndDelay(1, 80 * kFrameTimerMs)); |
| echo_chamber->AppendToPipe( |
| test::NewDuplicateAndDelay(1, 160 * kFrameTimerMs)); |
| |
| receiver_to_sender_->SetPacketPipe(std::move(echo_chamber)); |
| Create(); |
| StartBasicPlayer(); |
| |
| SetExpectedVideoPlayoutSmoothness( |
| base::Milliseconds(kFrameTimerMs) * 90 / 100, |
| base::Milliseconds(kFrameTimerMs) * 110 / 100, |
| base::Milliseconds(kFrameTimerMs) / 10); |
| |
| for (int frames_counter = 0; frames_counter < kLongTestIterations; |
| ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(kFrameTimerMs); |
| } |
| RunTasks(100 * kFrameTimerMs + 1); // Empty the pipeline. |
| |
| EXPECT_EQ(static_cast<size_t>(kLongTestIterations), video_ticks_.size()); |
| } |
| |
| TEST_F(End2EndTest, CAST_E2E_TEST(TestSetPlayoutDelay)) { |
| Configure(CODEC_VIDEO_FAKE, CODEC_AUDIO_PCM16); |
| video_sender_config_.min_playout_delay = |
| video_sender_config_.max_playout_delay; |
| audio_sender_config_.min_playout_delay = |
| audio_sender_config_.max_playout_delay; |
| video_sender_config_.max_playout_delay = base::Seconds(1); |
| audio_sender_config_.max_playout_delay = base::Seconds(1); |
| Create(); |
| StartBasicPlayer(); |
| const int kNewDelay = 600; |
| |
| int frames_counter = 0; |
| for (; frames_counter < 50; ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(kFrameTimerMs); |
| } |
| cast_sender_->SetTargetPlayoutDelay(base::Milliseconds(kNewDelay)); |
| for (; frames_counter < 100; ++frames_counter) { |
| SendVideoFrame(frames_counter, testing_clock_sender_.NowTicks()); |
| RunTasks(kFrameTimerMs); |
| } |
| RunTasks(100 * kFrameTimerMs + 1); // Empty the pipeline. |
| size_t jump = 0; |
| for (size_t i = 1; i < video_ticks_.size(); i++) { |
| int64_t delta = |
| (video_ticks_[i].second - video_ticks_[i - 1].second).InMilliseconds(); |
| if (delta > 100) { |
| EXPECT_EQ(kNewDelay - kTargetPlayoutDelayMs + kFrameTimerMs, delta); |
| EXPECT_EQ(0u, jump); |
| jump = i; |
| } |
| } |
| EXPECT_GT(jump, 49u); |
| EXPECT_LT(jump, 120u); |
| } |
| |
| } // namespace cast |
| } // namespace media |
| |
| #undef CAST_E2E_TEST |