/* * Copyright 2018 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"video/video_analyzer.h"
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 ::webrtc::test::GetGlobalMetricsLogger; using ::webrtc::test::ImprovementDirection; using ::webrtc::test::Metric; using ::webrtc::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(rtc::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. constchar* 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();
VideoSendStream::Stats send_stats = send_stream_->GetStats(); // It's not certain that we yet have estimates for any of these stats. // Check that they are positive before mixing them in. if (send_stats.encode_frame_rate > 0)
encode_frame_rate_.AddSample(send_stats.encode_frame_rate); if (send_stats.avg_encode_time_ms > 0)
encode_time_ms_.AddSample(send_stats.avg_encode_time_ms); if (send_stats.encode_usage_percent > 0)
encode_usage_percent_.AddSample(send_stats.encode_usage_percent); if (send_stats.media_bitrate_bps > 0)
media_bitrate_bps_.AddSample(send_stats.media_bitrate_bps);
size_t fec_bytes = 0; for (constauto& kv : send_stats.substreams) {
fec_bytes += kv.second.rtp_stats.fec.payload_bytes +
kv.second.rtp_stats.fec.padding_bytes;
}
fec_bitrate_bps_.AddSample((fec_bytes - last_fec_bytes_) * 8);
last_fec_bytes_ = fec_bytes;
if (receive_stream_ != nullptr) {
VideoReceiveStreamInterface::Stats receive_stats =
receive_stream_->GetStats();
// `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(receive_stats.decode_ms); if (receive_stats.max_decode_ms > 0)
decode_time_max_ms_.AddSample(receive_stats.max_decode_ms); if (receive_stats.width > 0 && receive_stats.height > 0) {
pixels_.AddSample(receive_stats.width * receive_stats.height);
}
// `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()) returnfalse;
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(); returnfalse;
} returntrue; // Try again.
}
StartExcludingCpuThreadTime();
PerformFrameComparison(comparison);
StopExcludingCpuThreadTime();
if (FrameProcessed()) {
done_.Set();
comparison_available_event_.Set(); returnfalse;
}
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()) returnfalse;
// 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(last_render_time_ - last_unfreeze_time_ms_);
#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. 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(comparison.send_time_ms - comparison.input_time_ms); 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(comparison.render_time_ms -
comparison.recv_time_ms);
network_time_.AddSample(comparison.recv_time_ms - comparison.send_time_ms);
}
end_to_end_.AddSample(comparison.render_time_ms - comparison.input_time_ms);
encoded_frame_size_.AddSample(comparison.encoded_frame_size);
}
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.