/* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include #include #include #include "gflags/gflags.h" #include "testing/gtest/include/gtest/gtest.h" #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" #include "webrtc/modules/rtp_rtcp/interface/rtp_header_parser.h" #include "webrtc/system_wrappers/interface/clock.h" #include "webrtc/system_wrappers/interface/critical_section_wrapper.h" #include "webrtc/system_wrappers/interface/event_wrapper.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/typedefs.h" #include "webrtc/video_engine/new_include/video_call.h" #include "webrtc/video_engine/test/common/direct_transport.h" #include "webrtc/video_engine/test/common/file_capturer.h" #include "webrtc/video_engine/test/common/frame_generator_capturer.h" #include "webrtc/video_engine/test/common/generate_ssrcs.h" #include "webrtc/video_engine/test/common/statistics.h" #include "webrtc/video_engine/test/common/video_renderer.h" DEFINE_int32(seconds, 10, "Seconds to run each clip."); namespace webrtc { struct FullStackTestParams { const char* test_label; struct { const char* name; size_t width, height; int fps; } clip; unsigned int bitrate; double avg_psnr_threshold; double avg_ssim_threshold; }; FullStackTestParams paris_qcif = {"net_delay_0_0_plr_0", {"paris_qcif", 176, 144, 30}, 300, 36.0, 0.96}; // TODO(pbos): Decide on psnr/ssim thresholds for foreman_cif. FullStackTestParams foreman_cif = {"foreman_cif_net_delay_0_0_plr_0", {"foreman_cif", 352, 288, 30}, 700, 0.0, 0.0}; class FullStackTest : public ::testing::TestWithParam { protected: std::map reserved_ssrcs_; }; class VideoAnalyzer : public PacketReceiver, public newapi::Transport, public VideoRenderer, public VideoSendStreamInput { public: VideoAnalyzer(VideoSendStreamInput* input, Transport* transport, VideoRenderer* loopback_video, const char* test_label, double avg_psnr_threshold, double avg_ssim_threshold, uint64_t duration_frames) : input_(input), transport_(transport), renderer_(loopback_video), receiver_(NULL), test_label_(test_label), rtp_timestamp_delta_(0), first_send_frame_(NULL), last_render_time_(0), avg_psnr_threshold_(avg_psnr_threshold), avg_ssim_threshold_(avg_ssim_threshold), frames_left_(duration_frames), crit_(CriticalSectionWrapper::CreateCriticalSection()), trigger_(EventWrapper::Create()) {} ~VideoAnalyzer() { while (!frames_.empty()) { delete frames_.back(); frames_.pop_back(); } while (!frame_pool_.empty()) { delete frame_pool_.back(); frame_pool_.pop_back(); } } virtual bool DeliverPacket(const uint8_t* packet, size_t length) OVERRIDE { scoped_ptr parser(RtpHeaderParser::Create()); RTPHeader header; parser->Parse(packet, static_cast(length), &header); { CriticalSectionScoped cs(crit_.get()); recv_times_[header.timestamp - rtp_timestamp_delta_] = Clock::GetRealTimeClock()->CurrentNtpInMilliseconds(); } return receiver_->DeliverPacket(packet, length); } virtual void PutFrame(const I420VideoFrame& video_frame, uint32_t delta_capture_ms) OVERRIDE { I420VideoFrame* copy = NULL; { CriticalSectionScoped cs(crit_.get()); if (frame_pool_.size() > 0) { copy = frame_pool_.front(); frame_pool_.pop_front(); } } if (copy == NULL) copy = new I420VideoFrame(); copy->CopyFrame(video_frame); copy->set_timestamp(copy->render_time_ms() * 90); { CriticalSectionScoped cs(crit_.get()); if (first_send_frame_ == NULL && rtp_timestamp_delta_ == 0) first_send_frame_ = copy; frames_.push_back(copy); } input_->PutFrame(video_frame, delta_capture_ms); } virtual bool SendRTP(const uint8_t* packet, size_t length) OVERRIDE { scoped_ptr parser(RtpHeaderParser::Create()); RTPHeader header; parser->Parse(packet, static_cast(length), &header); { CriticalSectionScoped cs(crit_.get()); if (rtp_timestamp_delta_ == 0) { rtp_timestamp_delta_ = header.timestamp - first_send_frame_->timestamp(); first_send_frame_ = NULL; } send_times_[header.timestamp - rtp_timestamp_delta_] = Clock::GetRealTimeClock()->CurrentNtpInMilliseconds(); } return transport_->SendRTP(packet, length); } virtual bool SendRTCP(const uint8_t* packet, size_t length) OVERRIDE { return transport_->SendRTCP(packet, length); } virtual void RenderFrame(const I420VideoFrame& video_frame, int time_to_render_ms) OVERRIDE { uint32_t send_timestamp = video_frame.timestamp() - rtp_timestamp_delta_; { CriticalSectionScoped cs(crit_.get()); while (frames_.front()->timestamp() < send_timestamp) { AddFrameComparison(frames_.front(), &last_rendered_frame_, true); frame_pool_.push_back(frames_.front()); frames_.pop_front(); } I420VideoFrame* reference_frame = frames_.front(); frames_.pop_front(); assert(reference_frame != NULL); assert(reference_frame->timestamp() == send_timestamp); AddFrameComparison(reference_frame, &video_frame, false); frame_pool_.push_back(reference_frame); if (--frames_left_ == 0) { PrintResult("psnr", psnr_, " dB"); PrintResult("ssim", ssim_, ""); PrintResult("sender_time", sender_time_, " ms"); PrintResult("receiver_time", receiver_time_, " ms"); PrintResult("total_delay_incl_network", end_to_end_, " ms"); PrintResult("time_between_rendered_frames", rendered_delta_, " ms"); EXPECT_GT(psnr_.Mean(), avg_psnr_threshold_); EXPECT_GT(ssim_.Mean(), avg_ssim_threshold_); trigger_->Set(); } } renderer_->RenderFrame(video_frame, time_to_render_ms); last_rendered_frame_.CopyFrame(video_frame); } void Wait() { trigger_->Wait(WEBRTC_EVENT_INFINITE); } VideoSendStreamInput* input_; Transport* transport_; VideoRenderer* renderer_; PacketReceiver* receiver_; private: void AddFrameComparison(const I420VideoFrame* reference_frame, const I420VideoFrame* render, bool dropped) { int64_t render_time = Clock::GetRealTimeClock()->CurrentNtpInMilliseconds(); psnr_.AddSample(I420PSNR(reference_frame, render)); ssim_.AddSample(I420SSIM(reference_frame, render)); if (dropped) return; if (last_render_time_ != 0) rendered_delta_.AddSample(render_time - last_render_time_); last_render_time_ = render_time; int64_t input_time = reference_frame->render_time_ms(); int64_t send_time = send_times_[reference_frame->timestamp()]; send_times_.erase(reference_frame->timestamp()); sender_time_.AddSample(send_time - input_time); int64_t recv_time = recv_times_[reference_frame->timestamp()]; recv_times_.erase(reference_frame->timestamp()); receiver_time_.AddSample(render_time - recv_time); end_to_end_.AddSample(render_time - input_time); } void PrintResult(const char* result_type, test::Statistics stats, const char* unit) { printf("RESULT %s: %s = {%f, %f}%s\n", result_type, test_label_, stats.Mean(), stats.StandardDeviation(), unit); } const char* test_label_; test::Statistics sender_time_; test::Statistics receiver_time_; test::Statistics psnr_; test::Statistics ssim_; test::Statistics end_to_end_; test::Statistics rendered_delta_; std::deque frames_; std::deque frame_pool_; I420VideoFrame last_rendered_frame_; std::map send_times_; std::map recv_times_; uint32_t rtp_timestamp_delta_; I420VideoFrame* first_send_frame_; int64_t last_render_time_; double avg_psnr_threshold_; double avg_ssim_threshold_; uint32_t frames_left_; scoped_ptr crit_; scoped_ptr trigger_; }; TEST_P(FullStackTest, DISABLED_NoPacketLoss) { FullStackTestParams params = GetParam(); scoped_ptr local_preview(test::VideoRenderer::Create( "Local Preview", params.clip.width, params.clip.height)); scoped_ptr loopback_video(test::VideoRenderer::Create( "Loopback Video", params.clip.width, params.clip.height)); test::DirectTransport transport; VideoAnalyzer analyzer( NULL, &transport, loopback_video.get(), params.test_label, params.avg_psnr_threshold, params.avg_ssim_threshold, static_cast(FLAGS_seconds * params.clip.fps)); VideoCall::Config call_config(&analyzer); scoped_ptr call(VideoCall::Create(call_config)); analyzer.receiver_ = call->Receiver(); transport.SetReceiver(&analyzer); VideoSendStream::Config send_config = call->GetDefaultSendConfig(); test::GenerateRandomSsrcs(&send_config, &reserved_ssrcs_); send_config.local_renderer = local_preview.get(); // TODO(pbos): static_cast shouldn't be required after mflodman refactors the // VideoCodec struct. send_config.codec.width = static_cast(params.clip.width); send_config.codec.height = static_cast(params.clip.height); send_config.codec.minBitrate = params.bitrate; send_config.codec.startBitrate = params.bitrate; send_config.codec.maxBitrate = params.bitrate; VideoSendStream* send_stream = call->CreateSendStream(send_config); analyzer.input_ = send_stream->Input(); Clock* test_clock = Clock::GetRealTimeClock(); scoped_ptr file_capturer( test::FrameGeneratorCapturer::Create( &analyzer, test::YuvFileFrameGenerator::Create( test::ResourcePath(params.clip.name, "yuv").c_str(), params.clip.width, params.clip.height, test_clock), params.clip.fps)); VideoReceiveStream::Config receive_config = call->GetDefaultReceiveConfig(); receive_config.rtp.ssrc = send_config.rtp.ssrcs[0]; receive_config.renderer = &analyzer; VideoReceiveStream* receive_stream = call->CreateReceiveStream(receive_config); receive_stream->StartReceive(); send_stream->StartSend(); file_capturer->Start(); analyzer.Wait(); file_capturer->Stop(); send_stream->StopSend(); receive_stream->StopReceive(); call->DestroyReceiveStream(receive_stream); call->DestroySendStream(send_stream); transport.StopSending(); } INSTANTIATE_TEST_CASE_P(FullStack, FullStackTest, ::testing::Values(paris_qcif, foreman_cif)); } // namespace webrtc