blob: 6767cc6d226cdf2083ec91855990eb949de36231 [file] [log] [blame]
/*
* Copyright (c) 2012 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 <cassert>
#include <string>
#include "modules/video_capture/main/interface/video_capture_factory.h"
#include "system_wrappers/interface/tick_util.h"
#include "testsupport/fileutils.h"
#include "testsupport/frame_reader.h"
#include "testsupport/frame_writer.h"
#include "video_engine/test/auto_test/helpers/vie_to_file_renderer.h"
#include "video_engine/test/auto_test/interface/tb_interfaces.h"
#include "video_engine/test/auto_test/interface/tb_external_transport.h"
#include "video_engine/test/auto_test/interface/vie_autotest.h"
#include "video_engine/test/auto_test/interface/vie_autotest_defines.h"
#include "video_engine/test/auto_test/primitives/framedrop_primitives.h"
#include "video_engine/test/auto_test/primitives/general_primitives.h"
// Tracks which frames are created on the local side and reports them to the
// FrameDropDetector class.
class CreatedTimestampEffectFilter : public webrtc::ViEEffectFilter {
public:
explicit CreatedTimestampEffectFilter(FrameDropDetector* frame_drop_detector)
: frame_drop_detector_(frame_drop_detector) {}
virtual ~CreatedTimestampEffectFilter() {}
virtual int Transform(int size, unsigned char* frameBuffer,
unsigned int timeStamp90KHz, unsigned int width,
unsigned int height) {
frame_drop_detector_->ReportFrameState(FrameDropDetector::kCreated,
timeStamp90KHz);
return 0;
}
private:
FrameDropDetector* frame_drop_detector_;
};
// Tracks which frames are sent in external transport on the local side
// and reports them to the FrameDropDetector class.
class FrameSentCallback : public SendFrameCallback {
public:
explicit FrameSentCallback(FrameDropDetector* frame_drop_detector)
: frame_drop_detector_(frame_drop_detector) {}
virtual ~FrameSentCallback() {}
virtual void FrameSent(unsigned int rtp_timestamp) {
frame_drop_detector_->ReportFrameState(FrameDropDetector::kSent,
rtp_timestamp);
}
private:
FrameDropDetector* frame_drop_detector_;
};
// Tracks which frames are received in external transport on the remote side
// and reports them to the FrameDropDetector class.
class FrameReceivedCallback : public ReceiveFrameCallback {
public:
explicit FrameReceivedCallback(FrameDropDetector* frame_drop_detector)
: frame_drop_detector_(frame_drop_detector) {}
virtual ~FrameReceivedCallback() {}
virtual void FrameReceived(unsigned int rtp_timestamp) {
frame_drop_detector_->ReportFrameState(FrameDropDetector::kReceived,
rtp_timestamp);
}
private:
FrameDropDetector* frame_drop_detector_;
};
// Tracks when frames are decoded on the remote side (received from the
// jitter buffer) and reports them to the FrameDropDetector class.
class DecodedTimestampEffectFilter : public webrtc::ViEEffectFilter {
public:
explicit DecodedTimestampEffectFilter(FrameDropDetector* frame_drop_detector)
: frame_drop_detector_(frame_drop_detector) {}
virtual ~DecodedTimestampEffectFilter() {}
virtual int Transform(int size, unsigned char* frameBuffer,
unsigned int timeStamp90KHz, unsigned int width,
unsigned int height) {
frame_drop_detector_->ReportFrameState(FrameDropDetector::kDecoded,
timeStamp90KHz);
return 0;
}
private:
FrameDropDetector* frame_drop_detector_;
};
void TestFullStack(const TbInterfaces& interfaces,
int capture_id,
int video_channel,
int width,
int height,
int bit_rate_kbps,
int packet_loss_percent,
int network_delay_ms,
FrameDropDetector* frame_drop_detector) {
webrtc::VideoEngine *video_engine_interface = interfaces.video_engine;
webrtc::ViEBase *base_interface = interfaces.base;
webrtc::ViECapture *capture_interface = interfaces.capture;
webrtc::ViERender *render_interface = interfaces.render;
webrtc::ViECodec *codec_interface = interfaces.codec;
webrtc::ViENetwork *network_interface = interfaces.network;
// ***************************************************************
// Engine ready. Begin testing class
// ***************************************************************
webrtc::VideoCodec video_codec;
memset(&video_codec, 0, sizeof (webrtc::VideoCodec));
// Set up all receive codecs. This basically setup the codec interface
// to be able to recognize all receive codecs based on payload type.
for (int idx = 0; idx < codec_interface->NumberOfCodecs(); idx++) {
EXPECT_EQ(0, codec_interface->GetCodec(idx, video_codec));
SetSuitableResolution(&video_codec, width, height);
EXPECT_EQ(0, codec_interface->SetReceiveCodec(video_channel, video_codec));
}
// Configure External transport to simulate network interference:
TbExternalTransport external_transport(*interfaces.network);
external_transport.SetPacketLoss(packet_loss_percent);
external_transport.SetNetworkDelay(network_delay_ms);
FrameSentCallback frame_sent_callback(frame_drop_detector);
FrameReceivedCallback frame_received_callback(frame_drop_detector);
external_transport.RegisterSendFrameCallback(&frame_sent_callback);
external_transport.RegisterReceiveFrameCallback(&frame_received_callback);
EXPECT_EQ(0, network_interface->RegisterSendTransport(video_channel,
external_transport));
EXPECT_EQ(0, base_interface->StartReceive(video_channel));
// Setup only the VP8 codec, which is what we'll use.
webrtc::VideoCodec codec;
EXPECT_TRUE(FindSpecificCodec(webrtc::kVideoCodecVP8, codec_interface,
&codec));
codec.startBitrate = bit_rate_kbps;
codec.maxBitrate = bit_rate_kbps;
codec.width = width;
codec.height = height;
EXPECT_EQ(0, codec_interface->SetSendCodec(video_channel, codec));
webrtc::ViEImageProcess *image_process =
webrtc::ViEImageProcess::GetInterface(video_engine_interface);
EXPECT_TRUE(image_process);
// Setup the effect filters
CreatedTimestampEffectFilter create_filter(frame_drop_detector);
EXPECT_EQ(0, image_process->RegisterSendEffectFilter(video_channel,
create_filter));
DecodedTimestampEffectFilter decode_filter(frame_drop_detector);
EXPECT_EQ(0, image_process->RegisterRenderEffectFilter(video_channel,
decode_filter));
// Send video.
EXPECT_EQ(0, base_interface->StartSend(video_channel));
AutoTestSleep(KAutoTestSleepTimeMs);
// Cleanup.
EXPECT_EQ(0, image_process->DeregisterSendEffectFilter(video_channel));
EXPECT_EQ(0, image_process->DeregisterRenderEffectFilter(video_channel));
image_process->Release();
ViETest::Log("Done!");
WebRtc_Word32 num_rtp_packets = 0;
WebRtc_Word32 num_dropped_packets = 0;
WebRtc_Word32 num_rtcp_packets = 0;
external_transport.GetStats(num_rtp_packets, num_dropped_packets,
num_rtcp_packets);
ViETest::Log("RTP packets : %5d", num_rtp_packets);
ViETest::Log("Dropped packets: %5d", num_dropped_packets);
ViETest::Log("RTCP packets : %5d", num_rtcp_packets);
// ***************************************************************
// Testing finished. Tear down Video Engine
// ***************************************************************
EXPECT_EQ(0, base_interface->StopSend(video_channel));
EXPECT_EQ(0, base_interface->StopReceive(video_channel));
EXPECT_EQ(0, network_interface->DeregisterSendTransport(video_channel));
EXPECT_EQ(0, render_interface->StopRender(capture_id));
EXPECT_EQ(0, render_interface->StopRender(video_channel));
EXPECT_EQ(0, render_interface->RemoveRenderer(capture_id));
EXPECT_EQ(0, render_interface->RemoveRenderer(video_channel));
EXPECT_EQ(0, capture_interface->DisconnectCaptureDevice(video_channel));
EXPECT_EQ(0, base_interface->DeleteChannel(video_channel));
}
void FixOutputFileForComparison(const std::string& output_file,
int frame_length_in_bytes,
const std::vector<Frame*>& frames) {
webrtc::test::FrameReaderImpl frame_reader(output_file,
frame_length_in_bytes);
const std::string temp_file = output_file + ".fixed";
webrtc::test::FrameWriterImpl frame_writer(temp_file, frame_length_in_bytes);
frame_reader.Init();
frame_writer.Init();
ASSERT_FALSE(frames.front()->dropped_at_render) << "It should not be "
"possible to drop the first frame. Both because we don't have anything "
"useful to fill that gap with and it is impossible to detect it without "
"any previous timestamps to compare with.";
WebRtc_UWord8* last_frame_data = new WebRtc_UWord8[frame_length_in_bytes];
// Process the file and write frame duplicates for all dropped frames.
for (std::vector<Frame*>::const_iterator it = frames.begin();
it != frames.end(); ++it) {
if ((*it)->dropped_at_render) {
// Write the previous frame to the output file:
EXPECT_TRUE(frame_writer.WriteFrame(last_frame_data));
} else {
EXPECT_TRUE(frame_reader.ReadFrame(last_frame_data));
EXPECT_TRUE(frame_writer.WriteFrame(last_frame_data));
}
}
delete[] last_frame_data;
frame_reader.Close();
frame_writer.Close();
ASSERT_EQ(0, std::remove(output_file.c_str()));
ASSERT_EQ(0, std::rename(temp_file.c_str(), output_file.c_str()));
}
void FrameDropDetector::ReportFrameState(State state, unsigned int timestamp) {
dirty_ = true;
switch (state) {
case kCreated: {
int number = created_frames_vector_.size();
Frame* frame = new Frame(number, timestamp);
frame->created_timestamp_in_us_ =
webrtc::TickTime::MicrosecondTimestamp();
created_frames_vector_.push_back(frame);
created_frames_[timestamp] = frame;
num_created_frames_++;
break;
}
case kSent:
sent_frames_[timestamp] = webrtc::TickTime::MicrosecondTimestamp();
if (timestamp_diff_ == 0) {
// When the first created frame arrives we calculate the fixed
// difference between the timestamps of the frames entering and leaving
// the encoder. This diff is used to identify the frames from the
// created_frames_ map.
timestamp_diff_ =
timestamp - created_frames_vector_.front()->frame_timestamp_;
}
num_sent_frames_++;
break;
case kReceived:
received_frames_[timestamp] = webrtc::TickTime::MicrosecondTimestamp();
num_received_frames_++;
break;
case kDecoded:
decoded_frames_[timestamp] = webrtc::TickTime::MicrosecondTimestamp();
num_decoded_frames_++;
break;
case kRendered:
rendered_frames_[timestamp] = webrtc::TickTime::MicrosecondTimestamp();
num_rendered_frames_++;
break;
}
}
void FrameDropDetector::CalculateResults() {
// Fill in all fields of the Frame objects in the created_frames_ map.
// Iterate over the maps from converted timestamps to the arrival timestamps.
std::map<unsigned int, int64_t>::const_iterator it;
for (it = sent_frames_.begin(); it != sent_frames_.end(); ++it) {
int created_timestamp = it->first - timestamp_diff_;
created_frames_[created_timestamp]->sent_timestamp_in_us_ = it->second;
}
for (it = received_frames_.begin(); it != received_frames_.end(); ++it) {
int created_timestamp = it->first - timestamp_diff_;
created_frames_[created_timestamp]->received_timestamp_in_us_ = it->second;
}
for (it = decoded_frames_.begin(); it != decoded_frames_.end(); ++it) {
int created_timestamp = it->first - timestamp_diff_;
created_frames_[created_timestamp]->decoded_timestamp_in_us_ =it->second;
}
for (it = rendered_frames_.begin(); it != rendered_frames_.end(); ++it) {
int created_timestamp = it->first - timestamp_diff_;
created_frames_[created_timestamp]->rendered_timestamp_in_us_ = it->second;
}
// Find out where the frames were not present in the different states.
dropped_frames_at_send_ = 0;
dropped_frames_at_receive_ = 0;
dropped_frames_at_decode_ = 0;
dropped_frames_at_render_ = 0;
for (std::vector<Frame*>::const_iterator it = created_frames_vector_.begin();
it != created_frames_vector_.end(); ++it) {
int encoded_timestamp = (*it)->frame_timestamp_ + timestamp_diff_;
if (sent_frames_.find(encoded_timestamp) == sent_frames_.end()) {
(*it)->dropped_at_send = true;
dropped_frames_at_send_++;
}
if (received_frames_.find(encoded_timestamp) == received_frames_.end()) {
(*it)->dropped_at_receive = true;
dropped_frames_at_receive_++;
}
if (decoded_frames_.find(encoded_timestamp) == decoded_frames_.end()) {
(*it)->dropped_at_decode = true;
dropped_frames_at_decode_++;
}
if (rendered_frames_.find(encoded_timestamp) == rendered_frames_.end()) {
(*it)->dropped_at_render = true;
dropped_frames_at_render_++;
}
}
dirty_ = false;
}
void FrameDropDetector::PrintReport() {
assert(!dirty_);
ViETest::Log("Frame Drop Detector report:");
ViETest::Log(" Created frames: %ld", created_frames_.size());
ViETest::Log(" Sent frames: %ld", sent_frames_.size());
ViETest::Log(" Received frames: %ld", received_frames_.size());
ViETest::Log(" Decoded frames: %ld", decoded_frames_.size());
ViETest::Log(" Rendered frames: %ld", rendered_frames_.size());
// Display all frames and stats for them:
long last_created = 0;
long last_sent = 0;
long last_received = 0;
long last_decoded = 0;
long last_rendered = 0;
ViETest::Log("\nDeltas between sent frames and drop status:");
ViETest::Log("Unit: Microseconds");
ViETest::Log("Frame Created Sent Received Decoded Rendered "
"Dropped at Dropped at Dropped at Dropped at");
ViETest::Log(" nbr delta delta delta delta delta "
" Send? Receive? Decode? Render?");
for (std::vector<Frame*>::const_iterator it = created_frames_vector_.begin();
it != created_frames_vector_.end(); ++it) {
int created_delta =
static_cast<int>((*it)->created_timestamp_in_us_ - last_created);
int sent_delta = (*it)->dropped_at_send ? -1 :
static_cast<int>((*it)->sent_timestamp_in_us_ - last_sent);
int received_delta = (*it)->dropped_at_receive ? -1 :
static_cast<int>((*it)->received_timestamp_in_us_ - last_received);
int decoded_delta = (*it)->dropped_at_decode ? -1 :
static_cast<int>((*it)->decoded_timestamp_in_us_ - last_decoded);
int rendered_delta = (*it)->dropped_at_render ? -1 :
static_cast<int>((*it)->rendered_timestamp_in_us_ - last_rendered);
// Set values to -1 for the first frame:
if ((*it)->number_ == 0) {
created_delta = -1;
sent_delta = -1;
received_delta = -1;
decoded_delta = -1;
rendered_delta = -1;
}
ViETest::Log("%5d %8d %8d %8d %8d %8d %10s %10s %10s %10s",
(*it)->number_,
created_delta,
sent_delta,
received_delta,
decoded_delta,
rendered_delta,
(*it)->dropped_at_send ? "DROPPED" : " ",
(*it)->dropped_at_receive ? "DROPPED" : " ",
(*it)->dropped_at_decode ? "DROPPED" : " ",
(*it)->dropped_at_render ? "DROPPED" : " ");
last_created = (*it)->created_timestamp_in_us_;
if (!(*it)->dropped_at_send) {
last_sent = (*it)->sent_timestamp_in_us_;
}
if (!(*it)->dropped_at_receive) {
last_received = (*it)->received_timestamp_in_us_;
}
if (!(*it)->dropped_at_decode) {
last_decoded = (*it)->decoded_timestamp_in_us_;
}
if (!(*it)->dropped_at_render) {
last_rendered = (*it)->rendered_timestamp_in_us_;
}
}
ViETest::Log("\nLatency between states (-1 means N/A because of drop):");
ViETest::Log("Unit: Microseconds");
ViETest::Log("Frame Created Sent Received Decoded Total "
" Total");
ViETest::Log(" nbr ->Sent ->Received ->Decoded ->Rendered latency "
" latency");
ViETest::Log(" (incl network)"
"(excl network)");
for (std::vector<Frame*>::const_iterator it = created_frames_vector_.begin();
it != created_frames_vector_.end(); ++it) {
int created_to_sent = (*it)->dropped_at_send ? -1 :
static_cast<int>((*it)->sent_timestamp_in_us_ -
(*it)->created_timestamp_in_us_);
int sent_to_received = (*it)->dropped_at_receive ? -1 :
static_cast<int>((*it)->received_timestamp_in_us_ -
(*it)->sent_timestamp_in_us_);
int received_to_decoded = (*it)->dropped_at_decode ? -1 :
static_cast<int>((*it)->decoded_timestamp_in_us_ -
(*it)->received_timestamp_in_us_);
int decoded_to_render = (*it)->dropped_at_render ? -1 :
static_cast<int>((*it)->rendered_timestamp_in_us_ -
(*it)->decoded_timestamp_in_us_);
int total_latency_incl_network = (*it)->dropped_at_render ? -1 :
static_cast<int>((*it)->rendered_timestamp_in_us_ -
(*it)->created_timestamp_in_us_);
int total_latency_excl_network = (*it)->dropped_at_render ? -1 :
static_cast<int>((*it)->rendered_timestamp_in_us_ -
(*it)->created_timestamp_in_us_ - sent_to_received);
ViETest::Log("%5d %9d %9d %9d %9d %12d %12d",
(*it)->number_,
created_to_sent,
sent_to_received,
received_to_decoded,
decoded_to_render,
total_latency_incl_network,
total_latency_excl_network);
}
// Find and print the dropped frames.
ViETest::Log("\nTotal # dropped frames at:");
ViETest::Log(" Send : %d", dropped_frames_at_send_);
ViETest::Log(" Receive: %d", dropped_frames_at_receive_);
ViETest::Log(" Decode : %d", dropped_frames_at_decode_);
ViETest::Log(" Render : %d", dropped_frames_at_render_);
}
void FrameDropDetector::PrintDebugDump() {
assert(!dirty_);
ViETest::Log("\nPrintDebugDump: Frame objects:");
ViETest::Log("Frame FrTimeStamp Created Sent Received Decoded"
" Rendered ");
for (std::vector<Frame*>::const_iterator it = created_frames_vector_.begin();
it != created_frames_vector_.end(); ++it) {
ViETest::Log("%5d %11d %11d %11d %11d %11d %11d",
(*it)->number_,
(*it)->frame_timestamp_,
(*it)->created_timestamp_in_us_,
(*it)->sent_timestamp_in_us_,
(*it)->received_timestamp_in_us_,
(*it)->decoded_timestamp_in_us_,
(*it)->rendered_timestamp_in_us_);
}
std::vector<int> mismatch_frame_num_list;
for (std::vector<Frame*>::const_iterator it = created_frames_vector_.begin();
it != created_frames_vector_.end(); ++it) {
if ((*it)->dropped_at_render != (*it)->dropped_at_decode) {
mismatch_frame_num_list.push_back((*it)->number_);
}
}
if (mismatch_frame_num_list.size() > 0) {
ViETest::Log("\nDecoded/Rendered mismatches:");
ViETest::Log("Frame FrTimeStamp Created Sent Received "
"Decoded Rendered ");
for (std::vector<int>::const_iterator it = mismatch_frame_num_list.begin();
it != mismatch_frame_num_list.end(); ++it) {
Frame* frame = created_frames_vector_[*it];
ViETest::Log("%5d %11d %11d %11d %11d %11d %11d",
frame->number_,
frame->frame_timestamp_,
frame->created_timestamp_in_us_,
frame->sent_timestamp_in_us_,
frame->received_timestamp_in_us_,
frame->decoded_timestamp_in_us_,
frame->rendered_timestamp_in_us_);
}
}
ViETest::Log("\nReportFrameState method invocations:");
ViETest::Log(" Created : %d", num_created_frames_);
ViETest::Log(" Send : %d", num_sent_frames_);
ViETest::Log(" Received: %d", num_received_frames_);
ViETest::Log(" Decoded : %d", num_decoded_frames_);
ViETest::Log(" Rendered: %d", num_rendered_frames_);
}
const std::vector<Frame*>& FrameDropDetector::GetAllFrames() {
assert(!dirty_);
return created_frames_vector_;
}
int FrameDropDetector::GetNumberOfFramesDroppedAt(State state) {
assert(!dirty_);
switch (state) {
case kSent:
return dropped_frames_at_send_;
case kReceived:
return dropped_frames_at_receive_;
case kDecoded:
return dropped_frames_at_decode_;
case kRendered:
return dropped_frames_at_render_;
default:
return 0;
}
}
int FrameDropMonitoringRemoteFileRenderer::DeliverFrame(
unsigned char *buffer, int buffer_size, uint32_t time_stamp,
int64_t render_time) {
// Register that this frame has been rendered:
frame_drop_detector_->ReportFrameState(FrameDropDetector::kRendered,
time_stamp);
return ViEToFileRenderer::DeliverFrame(buffer, buffer_size,
time_stamp, render_time);
}
int FrameDropMonitoringRemoteFileRenderer::FrameSizeChange(
unsigned int width, unsigned int height, unsigned int number_of_streams) {
return ViEToFileRenderer::FrameSizeChange(width, height, number_of_streams);
}