ABSL_FLAG(bool,
save_worst_frame,
false, "Enable saving a frame with the lowest PSNR to a jpeg file in the " "test_artifacts_dir");
namespace webrtc {
namespace {
using test::GetGlobalMetricsLogger; using test::ImprovementDirection; using test::Metric; using test::Unit;
constexpr TimeDelta kSendStatsPollingInterval = TimeDelta::Seconds(1);
constexpr size_t kMaxComparisons = 10; // How often is keep alive message printed.
constexpr TimeDelta kKeepAliveInterval = TimeDelta::Seconds(30); // Interval between checking that the test is over.
constexpr TimeDelta kProbingInterval = TimeDelta::Millis(500);
constexpr int kKeepAliveIntervalIterations =
kKeepAliveInterval.ms() / kProbingInterval.ms();
// Try to use about as many threads as cores, but leave kMinCoresLeft alone, // so that we don't accidentally starve "real" worker threads (codec etc). // Also, don't allocate more than kMaxComparisonThreads, even if there are // spare cores.
for (uint32_t i = 0; i < num_cores; ++i) {
comparison_thread_pool_.push_back(PlatformThread::SpawnJoinable(
[this] { while (CompareFrames()) {
}
}, "Analyzer"));
}
if (!rtp_dump_name.empty()) {
fprintf(stdout, "Writing rtp dump to %s\n", rtp_dump_name.c_str());
rtp_file_writer_.reset(test::RtpFileWriter::Create(
test::RtpFileWriter::kRtpDump, rtp_dump_name));
}
}
if (!IsFlexfec(packet.PayloadType()) &&
(packet.Ssrc() == ssrc_to_analyze_ ||
packet.Ssrc() == rtx_ssrc_to_analyze_)) { // Ignore FlexFEC timestamps, to avoid collisions with media timestamps. // (FlexFEC and media are sent on different SSRCs, which have different // timestamps spaces.) // Also ignore packets from wrong SSRC, but include retransmits.
MutexLock lock(&lock_);
int64_t timestamp =
wrap_handler_.Unwrap(packet.Timestamp() - rtp_timestamp_delta_);
recv_times_[timestamp] = clock_->CurrentNtpInMilliseconds();
}
if (!IsFlexfec(rtp_packet.PayloadType()) &&
rtp_packet.Ssrc() == ssrc_to_analyze_) { // Ignore FlexFEC timestamps, to avoid collisions with media timestamps. // (FlexFEC and media are sent on different SSRCs, which have different // timestamps spaces.) // Also ignore packets from wrong SSRC and retransmits.
int64_t timestamp =
wrap_handler_.Unwrap(rtp_packet.Timestamp() - rtp_timestamp_delta_);
send_times_[timestamp] = current_time;
while (wrap_handler_.Unwrap(frames_.front().rtp_timestamp()) <
send_timestamp) {
if (!last_rendered_frame_) { // No previous frame rendered, this one was dropped after sending but // before rendering.
++dropped_frames_before_rendering_;
} else {
AddFrameComparison(frames_.front(), *last_rendered_frame_, true,
render_time_ms);
}
frames_.pop_front();
RTC_DCHECK(!frames_.empty());
}
VideoFrame reference_frame = frames_.front();
frames_.pop_front();
int64_t reference_timestamp =
wrap_handler_.Unwrap(reference_frame.rtp_timestamp());
if (send_timestamp == reference_timestamp - 1) { // TODO(ivica): Make this work for > 2 streams. // Look at RTPSender::BuildRTPHeader.
++send_timestamp;
}
ASSERT_EQ(reference_timestamp, send_timestamp);
void VideoAnalyzer::Wait() { // Frame comparisons can be very expensive. Wait for test to be done, but // at time-out check if frames_processed is going up. If so, give it more // time, otherwise fail. Hopefully this will reduce test flakiness.
int last_frames_processed = -1;
int last_frames_captured = -1;
int iteration = 0;
while (!done_.Wait(kProbingInterval)) {
int frames_processed;
int frames_captured;
{
MutexLock lock(&comparison_lock_);
frames_processed = frames_processed_;
frames_captured = captured_frames_;
}
// Print some output so test infrastructure won't think we've crashed. const char* kKeepAliveMessages[3] = { "Uh, I'm-I'm not quite dead, sir.", "Uh, I-I think uh, I could pull through, sir.", "Actually, I think I'm all right to come with you--"};
if (++iteration % kKeepAliveIntervalIterations == 0) {
printf("- %s\n", kKeepAliveMessages[iteration % 3]);
}
void VideoAnalyzer::PollStats() { // Do not grab `comparison_lock_`, before `GetStats()` completes. // Otherwise a deadlock may occur: // 1) `comparison_lock_` is acquired after `lock_` // 2) `lock_` is acquired after internal pacer lock in SendRtp() // 3) internal pacer lock is acquired by GetStats().
Call::Stats call_stats = call_->GetStats();
Timestamp now = clock_->CurrentTime();
// `total_decode_time_ms` gives a good estimate of the mean decode time, // `decode_ms` is used to keep track of the standard deviation.
if (receive_stats.frames_decoded > 0)
mean_decode_time_ms_ = receive_stats.total_decode_time.ms<double>() /
receive_stats.frames_decoded;
if (receive_stats.decode_ms > 0)
decode_time_ms_.AddSample(
{.value = static_cast<double>(receive_stats.decode_ms), .time = now});
if (receive_stats.max_decode_ms > 0)
decode_time_max_ms_.AddSample(
{.value = static_cast<double>(receive_stats.max_decode_ms),
.time = now});
if (receive_stats.width > 0 && receive_stats.height > 0) {
pixels_.AddSample({.value = static_cast<double>(receive_stats.width *
receive_stats.height),
.time = now});
}
// `frames_decoded` and `frames_rendered` are used because they are more // accurate than `decode_frame_rate` and `render_frame_rate`. // The latter two are calculated on a momentary basis.
if (total_inter_frame_delay_ > 0) {
decode_frame_rate_ =
receive_stats.frames_decoded / total_inter_frame_delay_;
render_frame_rate_ =
receive_stats.frames_rendered / total_inter_frame_delay_;
}
}
bool VideoAnalyzer::CompareFrames() {
if (AllFramesRecorded()) return false;
FrameComparison comparison;
if (!PopComparison(&comparison)) { // Wait until new comparison task is available, or test is done. // If done, wake up remaining threads waiting.
comparison_available_event_.Wait(TimeDelta::Seconds(1));
if (AllFramesRecorded()) {
comparison_available_event_.Set(); return false;
} returntrue; // Try again.
}
StartExcludingCpuThreadTime();
PerformFrameComparison(comparison);
StopExcludingCpuThreadTime();
if (FrameProcessed()) {
done_.Set();
comparison_available_event_.Set(); return false;
}
returntrue;
}
bool VideoAnalyzer::PopComparison(VideoAnalyzer::FrameComparison* comparison) {
MutexLock lock(&comparison_lock_); // If AllFramesRecorded() is true, it means we have already popped // frames_to_process_ frames from comparisons_, so there is no more work // for this thread to be done. frames_processed_ might still be lower if // all comparisons are not done, but those frames are currently being // worked on by other threads.
if (comparisons_.empty() || AllFramesRecordedLocked()) return false;
// Record the time from the last freeze until the last rendered frame to // ensure we cover the full timespan of the session. Otherwise the metric // would penalize an early freeze followed by no freezes until the end.
time_between_freezes_.AddSample(
{.value = static_cast<double>(last_render_time_ - last_unfreeze_time_ms_),
.time = clock_->CurrentTime()});
#ifdefined(WEBRTC_WIN) // On Linux and Mac in Resident Set some unused pages may be counted. // Therefore this metric will depend on order in which tests are run and // will be flaky.
PrintResult("memory_usage", memory_usage_, Unit::kBytes,
ImprovementDirection::kSmallerIsBetter); #endif
// Saving only the worst frame for manual analysis. Intention here is to // only detect video corruptions and not to track picture quality. Thus, // jpeg is used here.
if (absl::GetFlag(FLAGS_save_worst_frame) && worst_frame_) {
std::string output_dir;
test::GetTestArtifactsDir(&output_dir);
std::string output_path =
test::JoinFilename(output_dir, test_label_ + ".jpg");
RTC_LOG(LS_INFO) << "Saving worst frame to " << output_path;
test::JpegFrameWriter frame_writer(output_path);
RTC_CHECK(
frame_writer.WriteFrame(worst_frame_->frame, 100/*best quality*/));
}
// Disable quality check for quick test, as quality checks may fail // because too few samples were collected.
if (!is_quick_test_enabled_) {
EXPECT_GT(psnr_.GetAverage(), avg_psnr_threshold_);
EXPECT_GT(ssim_.GetAverage(), avg_ssim_threshold_);
}
}
void VideoAnalyzer::PerformFrameComparison( const VideoAnalyzer::FrameComparison& comparison) { // Perform expensive psnr and ssim calculations while not holding lock.
Timestamp now = clock_->CurrentTime(); double psnr = -1.0; double ssim = -1.0;
if (comparison.reference && !comparison.dropped) {
psnr = I420PSNR(&*comparison.reference, &*comparison.render);
ssim = I420SSIM(&*comparison.reference, &*comparison.render);
}
sender_time_.AddSample(
{.value = static_cast<double>(comparison.send_time_ms -
comparison.input_time_ms),
.time = now});
if (comparison.recv_time_ms > 0) { // If recv_time_ms == 0, this frame consisted of a packets which were all // lost in the transport. Since we were able to render the frame, however, // the dropped packets were recovered by FlexFEC. The FlexFEC recovery // happens internally in Call, and we can therefore here not know which // FEC packets that protected the lost media packets. Consequently, we // were not able to record a meaningful recv_time_ms. We therefore skip // this sample. // // The reasoning above does not hold for ULPFEC and RTX, as for those // strategies the timestamp of the received packets is set to the // timestamp of the protected/retransmitted media packet. I.e., then // recv_time_ms != 0, even though the media packets were lost.
receiver_time_.AddSample(
{.value = static_cast<double>(comparison.render_time_ms -
comparison.recv_time_ms),
.time = now});
network_time_.AddSample(
{.value = static_cast<double>(comparison.recv_time_ms -
comparison.send_time_ms),
.time = now});
}
end_to_end_.AddSample(
{.value = static_cast<double>(comparison.render_time_ms -
comparison.input_time_ms),
.time = now});
encoded_frame_size_.AddSample(
{.value = static_cast<double>(comparison.encoded_frame_size),
.time = now});
}
void VideoAnalyzer::PrintResult(absl::string_view result_type, const SamplesStatsCounter& stats,
Unit unit,
ImprovementDirection improvement_direction) {
GetGlobalMetricsLogger()->LogMetric(result_type, test_label_, stats, unit,
improvement_direction);
}
void VideoAnalyzer::PrintResultWithExternalMean(
absl::string_view result_type, double mean, const SamplesStatsCounter& stats,
Unit unit,
ImprovementDirection improvement_direction) { // If the true mean is different than the sample mean, the sample variance is // too low. The sample variance given a known mean is obtained by adding the // squared error between the true mean and the sample mean. double compensated_variance =
stats.IsEmpty()
? 0.0
: stats.GetVariance() + pow(mean - stats.GetAverage(), 2.0);
GetGlobalMetricsLogger()->LogMetric(
result_type, test_label_,
Metric::Stats{.mean = mean, .stddev = std::sqrt(compensated_variance)},
unit, improvement_direction);
}
void VideoAnalyzer::PrintSamplesToFile() {
FILE* out = graph_data_output_file_;
MutexLock lock(&comparison_lock_);
absl::c_sort(samples_, [](const Sample& A, const Sample& B) -> bool { return A.input_time_ms < B.input_time_ms;
});
// TODO(ivica): Make this work for > 2 streams. auto it = encoded_frame_sizes_.find(reference_timestamp);
if (it == encoded_frame_sizes_.end())
it = encoded_frame_sizes_.find(reference_timestamp - 1);
size_t encoded_size = it == encoded_frame_sizes_.end() ? 0 : it->second;
if (it != encoded_frame_sizes_.end())
encoded_frame_sizes_.erase(it);
void VideoAnalyzer::CapturedFrameForwarder::OnFrame( const VideoFrame& video_frame) {
VideoFrame copy = video_frame; // Frames from the capturer does not have a rtp timestamp. // Create one so it can be used for comparison.
RTC_DCHECK_EQ(0, video_frame.rtp_timestamp());
if (video_frame.ntp_time_ms() == 0)
copy.set_ntp_time_ms(clock_->CurrentNtpInMilliseconds());
copy.set_rtp_timestamp(copy.ntp_time_ms() * 90);
analyzer_->AddCapturedFrameForComparison(copy);
MutexLock lock(&lock_);
++captured_frames_;
if (send_stream_input_ && clock_->CurrentTime() <= test_end_ &&
captured_frames_ <= frames_to_capture_) {
send_stream_input_->OnFrame(copy);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.