diff options
author | pbos@webrtc.org <pbos@webrtc.org@4adac7df-926f-26a2-2b94-8c16560cd09d> | 2013-10-28 16:32:01 +0000 |
---|---|---|
committer | pbos@webrtc.org <pbos@webrtc.org@4adac7df-926f-26a2-2b94-8c16560cd09d> | 2013-10-28 16:32:01 +0000 |
commit | 24e2089750e9e51228b82d6c7ebf4fa064c797ba (patch) | |
tree | f964abcaca5d5609cbf1f7bcbbcbc1d8394c35c5 /video/full_stack.cc | |
parent | 4ce759057ec1d71b19bd706c5cdc9027096463fd (diff) | |
download | webrtc-24e2089750e9e51228b82d6c7ebf4fa064c797ba.tar.gz |
Separate Call API/build files from video_engine/.
BUG=2535
R=andrew@webrtc.org, mflodman@webrtc.org, niklas.enbom@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/2659004
git-svn-id: http://webrtc.googlecode.com/svn/trunk/webrtc@5042 4adac7df-926f-26a2-2b94-8c16560cd09d
Diffstat (limited to 'video/full_stack.cc')
-rw-r--r-- | video/full_stack.cc | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/video/full_stack.cc b/video/full_stack.cc new file mode 100644 index 00000000..b154df30 --- /dev/null +++ b/video/full_stack.cc @@ -0,0 +1,448 @@ +/* + * 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 <stdio.h> + +#include <deque> +#include <map> + +#include "gflags/gflags.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/call.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/system_wrappers/interface/sleep.h" +#include "webrtc/test/testsupport/fileutils.h" +#include "webrtc/test/direct_transport.h" +#include "webrtc/test/frame_generator_capturer.h" +#include "webrtc/test/generate_ssrcs.h" +#include "webrtc/test/statistics.h" +#include "webrtc/test/video_renderer.h" +#include "webrtc/typedefs.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<FullStackTestParams> { + protected: + std::map<uint32_t, bool> reserved_ssrcs_; +}; + +class VideoAnalyzer : public PacketReceiver, + public newapi::Transport, + public VideoRenderer, + public VideoSendStreamInput { + public: + VideoAnalyzer(VideoSendStreamInput* input, + Transport* transport, + const char* test_label, + double avg_psnr_threshold, + double avg_ssim_threshold, + int duration_frames) + : input_(input), + transport_(transport), + receiver_(NULL), + test_label_(test_label), + dropped_frames_(0), + 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()), + comparison_lock_(CriticalSectionWrapper::CreateCriticalSection()), + comparison_thread_(ThreadWrapper::CreateThread(&FrameComparisonThread, + this)), + trigger_(EventWrapper::Create()) { + unsigned int id; + EXPECT_TRUE(comparison_thread_->Start(id)); + } + + ~VideoAnalyzer() { + EXPECT_TRUE(comparison_thread_->Stop()); + + while (!frames_.empty()) { + delete frames_.back(); + frames_.pop_back(); + } + while (!frame_pool_.empty()) { + delete frame_pool_.back(); + frame_pool_.pop_back(); + } + } + + virtual void SetReceiver(PacketReceiver* receiver) { receiver_ = receiver; } + + virtual bool DeliverPacket(const uint8_t* packet, size_t length) OVERRIDE { + scoped_ptr<RtpHeaderParser> parser(RtpHeaderParser::Create()); + RTPHeader header; + parser->Parse(packet, static_cast<int>(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<RtpHeaderParser> parser(RtpHeaderParser::Create()); + RTPHeader header; + parser->Parse(packet, static_cast<int>(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 { + int64_t render_time_ms = + Clock::GetRealTimeClock()->CurrentNtpInMilliseconds(); + 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, render_time_ms); + frame_pool_.push_back(frames_.front()); + frames_.pop_front(); + } + + I420VideoFrame* reference_frame = frames_.front(); + frames_.pop_front(); + assert(reference_frame != NULL); + EXPECT_EQ(reference_frame->timestamp(), send_timestamp); + assert(reference_frame->timestamp() == send_timestamp); + + AddFrameComparison(reference_frame, &video_frame, false, render_time_ms); + frame_pool_.push_back(reference_frame); + } + + last_rendered_frame_.CopyFrame(video_frame); + } + + void Wait() { trigger_->Wait(120 * 1000); } + + VideoSendStreamInput* input_; + Transport* transport_; + PacketReceiver* receiver_; + + private: + struct FrameComparison { + FrameComparison(const I420VideoFrame* reference, + const I420VideoFrame* render, + bool dropped, + int64_t send_time_ms, + int64_t recv_time_ms, + int64_t render_time_ms) + : dropped(dropped), + send_time_ms(send_time_ms), + recv_time_ms(recv_time_ms), + render_time_ms(render_time_ms) { + this->reference.CopyFrame(*reference); + this->render.CopyFrame(*render); + } + + FrameComparison(const FrameComparison& compare) + : dropped(compare.dropped), + send_time_ms(compare.send_time_ms), + recv_time_ms(compare.recv_time_ms), + render_time_ms(compare.render_time_ms) { + this->reference.CopyFrame(compare.reference); + this->render.CopyFrame(compare.render); + } + + ~FrameComparison() {} + + I420VideoFrame reference; + I420VideoFrame render; + bool dropped; + int64_t send_time_ms; + int64_t recv_time_ms; + int64_t render_time_ms; + }; + + void AddFrameComparison(const I420VideoFrame* reference, + const I420VideoFrame* render, + bool dropped, + int64_t render_time_ms) { + int64_t send_time_ms = send_times_[reference->timestamp()]; + send_times_.erase(reference->timestamp()); + int64_t recv_time_ms = recv_times_[reference->timestamp()]; + recv_times_.erase(reference->timestamp()); + + CriticalSectionScoped crit(comparison_lock_.get()); + comparisons_.push_back(FrameComparison(reference, + render, + dropped, + send_time_ms, + recv_time_ms, + render_time_ms)); + } + + static bool FrameComparisonThread(void* obj) { + return static_cast<VideoAnalyzer*>(obj)->CompareFrames(); + } + + bool CompareFrames() { + assert(frames_left_ > 0); + + I420VideoFrame reference; + I420VideoFrame render; + bool dropped; + int64_t send_time_ms; + int64_t recv_time_ms; + int64_t render_time_ms; + + SleepMs(10); + + while (true) { + { + CriticalSectionScoped crit(comparison_lock_.get()); + if (comparisons_.empty()) + return true; + reference.SwapFrame(&comparisons_.front().reference); + render.SwapFrame(&comparisons_.front().render); + dropped = comparisons_.front().dropped; + send_time_ms = comparisons_.front().send_time_ms; + recv_time_ms = comparisons_.front().recv_time_ms; + render_time_ms = comparisons_.front().render_time_ms; + comparisons_.pop_front(); + } + + PerformFrameComparison(&reference, + &render, + dropped, + send_time_ms, + recv_time_ms, + render_time_ms); + + if (--frames_left_ == 0) { + PrintResult("psnr", psnr_, " dB"); + PrintResult("ssim", ssim_, ""); + PrintResult("sender_time", sender_time_, " ms"); + printf( + "RESULT dropped_frames: %s = %d\n", test_label_, dropped_frames_); + 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(); + + return false; + } + } + } + + void PerformFrameComparison(const I420VideoFrame* reference, + const I420VideoFrame* render, + bool dropped, + int64_t send_time_ms, + int64_t recv_time_ms, + int64_t render_time_ms) { + psnr_.AddSample(I420PSNR(reference, render)); + ssim_.AddSample(I420SSIM(reference, render)); + if (dropped) { + ++dropped_frames_; + return; + } + if (last_render_time_ != 0) + rendered_delta_.AddSample(render_time_ms - last_render_time_); + last_render_time_ = render_time_ms; + + int64_t input_time_ms = reference->render_time_ms(); + sender_time_.AddSample(send_time_ms - input_time_ms); + receiver_time_.AddSample(render_time_ms - recv_time_ms); + end_to_end_.AddSample(render_time_ms - input_time_ms); + } + + 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_; + + int dropped_frames_; + std::deque<I420VideoFrame*> frames_; + std::deque<I420VideoFrame*> frame_pool_; + I420VideoFrame last_rendered_frame_; + std::map<uint32_t, int64_t> send_times_; + std::map<uint32_t, int64_t> recv_times_; + uint32_t rtp_timestamp_delta_; + I420VideoFrame* first_send_frame_; + int64_t last_render_time_; + double avg_psnr_threshold_; + double avg_ssim_threshold_; + int frames_left_; + scoped_ptr<CriticalSectionWrapper> crit_; + scoped_ptr<CriticalSectionWrapper> comparison_lock_; + scoped_ptr<ThreadWrapper> comparison_thread_; + std::deque<FrameComparison> comparisons_; + scoped_ptr<EventWrapper> trigger_; +}; + +TEST_P(FullStackTest, NoPacketLoss) { + FullStackTestParams params = GetParam(); + + test::DirectTransport transport; + VideoAnalyzer analyzer(NULL, + &transport, + params.test_label, + params.avg_psnr_threshold, + params.avg_ssim_threshold, + FLAGS_seconds * params.clip.fps); + + Call::Config call_config(&analyzer); + + scoped_ptr<Call> call(Call::Create(call_config)); + analyzer.SetReceiver(call->Receiver()); + transport.SetReceiver(&analyzer); + + VideoSendStream::Config send_config = call->GetDefaultSendConfig(); + test::GenerateRandomSsrcs(&send_config, &reserved_ssrcs_); + + // TODO(pbos): static_cast shouldn't be required after mflodman refactors the + // VideoCodec struct. + send_config.codec.width = static_cast<uint16_t>(params.clip.width); + send_config.codec.height = static_cast<uint16_t>(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(); + + scoped_ptr<test::FrameGeneratorCapturer> file_capturer( + test::FrameGeneratorCapturer::CreateFromYuvFile( + &analyzer, + test::ResourcePath(params.clip.name, "yuv").c_str(), + params.clip.width, + params.clip.height, + params.clip.fps, + Clock::GetRealTimeClock())); + ASSERT_TRUE(file_capturer.get() != NULL) + << "Could not create capturer for " << params.clip.name + << ".yuv. Is this resource file present?"; + + 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 |