Clean up Stats class used in VideoProcessor tests.
* Reorder and rename members of FrameStatistic. * Rename corresponding temp variables used in the summary calculations. * Make Stats class less structy. * Try to enforce consistent frame numbers at all times. * Remove VideoProcessor::FrameInfo struct and use the Stats class for keeping track of encode/decode start times, etc. BUG=webrtc:6634 Review-Url: https://codereview.webrtc.org/3011923002 Cr-Commit-Position: refs/heads/master@{#19703}
This commit is contained in:
@ -12,88 +12,93 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <algorithm> // min_element, max_element
|
#include <algorithm>
|
||||||
|
|
||||||
#include "webrtc/rtc_base/checks.h"
|
#include "webrtc/rtc_base/checks.h"
|
||||||
#include "webrtc/rtc_base/format_macros.h"
|
#include "webrtc/rtc_base/format_macros.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
bool LessForEncodeTime(const FrameStatistic& s1, const FrameStatistic& s2) {
|
bool LessForEncodeTime(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||||
return s1.encode_time_in_us < s2.encode_time_in_us;
|
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||||
|
return s1.encode_time_us < s2.encode_time_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LessForDecodeTime(const FrameStatistic& s1, const FrameStatistic& s2) {
|
bool LessForDecodeTime(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||||
return s1.decode_time_in_us < s2.decode_time_in_us;
|
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||||
|
return s1.decode_time_us < s2.decode_time_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LessForEncodedSize(const FrameStatistic& s1, const FrameStatistic& s2) {
|
bool LessForEncodedSize(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||||
return s1.encoded_frame_length_in_bytes < s2.encoded_frame_length_in_bytes;
|
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||||
|
return s1.encoded_frame_size_bytes < s2.encoded_frame_size_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LessForBitRate(const FrameStatistic& s1, const FrameStatistic& s2) {
|
bool LessForBitRate(const FrameStatistic& s1, const FrameStatistic& s2) {
|
||||||
return s1.bit_rate_in_kbps < s2.bit_rate_in_kbps;
|
RTC_DCHECK_NE(s1.frame_number, s2.frame_number);
|
||||||
|
return s1.bitrate_kbps < s2.bitrate_kbps;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Stats::Stats() {}
|
FrameStatistic* Stats::AddFrame() {
|
||||||
|
// We don't expect more frames than what can be stored in an int.
|
||||||
Stats::~Stats() {}
|
stats_.emplace_back(static_cast<int>(stats_.size()));
|
||||||
|
return &stats_.back();
|
||||||
FrameStatistic& Stats::NewFrame(int frame_number) {
|
|
||||||
RTC_DCHECK_GE(frame_number, 0);
|
|
||||||
FrameStatistic stat;
|
|
||||||
stat.frame_number = frame_number;
|
|
||||||
stats_.push_back(stat);
|
|
||||||
return stats_[frame_number];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stats::PrintSummary() {
|
FrameStatistic* Stats::GetFrame(int frame_number) {
|
||||||
|
RTC_CHECK_GE(frame_number, 0);
|
||||||
|
RTC_CHECK_LT(frame_number, stats_.size());
|
||||||
|
return &stats_[frame_number];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Stats::size() const {
|
||||||
|
return stats_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stats::PrintSummary() const {
|
||||||
if (stats_.empty()) {
|
if (stats_.empty()) {
|
||||||
printf("No frame statistics have been logged yet.\n");
|
printf("No frame statistics have been logged yet.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate min, max, average and total encoding time.
|
// Calculate min, max, average and total encoding time.
|
||||||
int total_encoding_time_in_us = 0;
|
int total_encoding_time_us = 0;
|
||||||
int total_decoding_time_in_us = 0;
|
int total_decoding_time_us = 0;
|
||||||
int total_qp = 0;
|
size_t total_encoded_frame_size_bytes = 0;
|
||||||
int total_qp_count = 0;
|
size_t total_encoded_key_frame_size_bytes = 0;
|
||||||
size_t total_encoded_frames_lengths = 0;
|
size_t total_encoded_delta_frame_size_bytes = 0;
|
||||||
size_t total_encoded_key_frames_lengths = 0;
|
size_t num_key_frames = 0;
|
||||||
size_t total_encoded_nonkey_frames_lengths = 0;
|
size_t num_delta_frames = 0;
|
||||||
size_t num_keyframes = 0;
|
|
||||||
size_t num_nonkeyframes = 0;
|
|
||||||
|
|
||||||
for (const FrameStatistic& stat : stats_) {
|
for (const FrameStatistic& stat : stats_) {
|
||||||
total_encoding_time_in_us += stat.encode_time_in_us;
|
total_encoding_time_us += stat.encode_time_us;
|
||||||
total_decoding_time_in_us += stat.decode_time_in_us;
|
total_decoding_time_us += stat.decode_time_us;
|
||||||
total_encoded_frames_lengths += stat.encoded_frame_length_in_bytes;
|
total_encoded_frame_size_bytes += stat.encoded_frame_size_bytes;
|
||||||
if (stat.frame_type == webrtc::kVideoFrameKey) {
|
if (stat.frame_type == webrtc::kVideoFrameKey) {
|
||||||
total_encoded_key_frames_lengths += stat.encoded_frame_length_in_bytes;
|
total_encoded_key_frame_size_bytes += stat.encoded_frame_size_bytes;
|
||||||
++num_keyframes;
|
++num_key_frames;
|
||||||
} else {
|
} else {
|
||||||
total_encoded_nonkey_frames_lengths += stat.encoded_frame_length_in_bytes;
|
total_encoded_delta_frame_size_bytes += stat.encoded_frame_size_bytes;
|
||||||
++num_nonkeyframes;
|
++num_delta_frames;
|
||||||
}
|
|
||||||
if (stat.qp >= 0) {
|
|
||||||
total_qp += stat.qp;
|
|
||||||
++total_qp_count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encoding stats.
|
// Encoding stats.
|
||||||
printf("Encoding time:\n");
|
printf("Encoding time:\n");
|
||||||
FrameStatisticsIterator frame;
|
auto frame_it =
|
||||||
frame = std::min_element(stats_.begin(), stats_.end(), LessForEncodeTime);
|
std::min_element(stats_.begin(), stats_.end(), LessForEncodeTime);
|
||||||
printf(" Min : %7d us (frame %d)\n", frame->encode_time_in_us,
|
printf(" Min : %7d us (frame %d)\n", frame_it->encode_time_us,
|
||||||
frame->frame_number);
|
frame_it->frame_number);
|
||||||
frame = std::max_element(stats_.begin(), stats_.end(), LessForEncodeTime);
|
frame_it = std::max_element(stats_.begin(), stats_.end(), LessForEncodeTime);
|
||||||
printf(" Max : %7d us (frame %d)\n", frame->encode_time_in_us,
|
printf(" Max : %7d us (frame %d)\n", frame_it->encode_time_us,
|
||||||
frame->frame_number);
|
frame_it->frame_number);
|
||||||
printf(" Average : %7d us\n",
|
printf(" Average : %7d us\n",
|
||||||
static_cast<int>(total_encoding_time_in_us / stats_.size()));
|
static_cast<int>(total_encoding_time_us / stats_.size()));
|
||||||
|
|
||||||
// Decoding stats.
|
// Decoding stats.
|
||||||
printf("Decoding time:\n");
|
printf("Decoding time:\n");
|
||||||
@ -107,57 +112,66 @@ void Stats::PrintSummary() {
|
|||||||
if (decoded_frames.empty()) {
|
if (decoded_frames.empty()) {
|
||||||
printf("No successfully decoded frames exist in this statistics.\n");
|
printf("No successfully decoded frames exist in this statistics.\n");
|
||||||
} else {
|
} else {
|
||||||
frame = std::min_element(decoded_frames.begin(), decoded_frames.end(),
|
frame_it = std::min_element(decoded_frames.begin(), decoded_frames.end(),
|
||||||
LessForDecodeTime);
|
LessForDecodeTime);
|
||||||
printf(" Min : %7d us (frame %d)\n", frame->decode_time_in_us,
|
printf(" Min : %7d us (frame %d)\n", frame_it->decode_time_us,
|
||||||
frame->frame_number);
|
frame_it->frame_number);
|
||||||
frame = std::max_element(decoded_frames.begin(), decoded_frames.end(),
|
frame_it = std::max_element(decoded_frames.begin(), decoded_frames.end(),
|
||||||
LessForDecodeTime);
|
LessForDecodeTime);
|
||||||
printf(" Max : %7d us (frame %d)\n", frame->decode_time_in_us,
|
printf(" Max : %7d us (frame %d)\n", frame_it->decode_time_us,
|
||||||
frame->frame_number);
|
frame_it->frame_number);
|
||||||
printf(" Average : %7d us\n",
|
printf(" Average : %7d us\n",
|
||||||
static_cast<int>(total_decoding_time_in_us / decoded_frames.size()));
|
static_cast<int>(total_decoding_time_us / decoded_frames.size()));
|
||||||
printf(" Failures: %d frames failed to decode.\n",
|
printf(" Failures: %d frames failed to decode.\n",
|
||||||
static_cast<int>(stats_.size() - decoded_frames.size()));
|
static_cast<int>(stats_.size() - decoded_frames.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame size stats.
|
// Frame size stats.
|
||||||
printf("Frame sizes:\n");
|
printf("Frame sizes:\n");
|
||||||
frame = std::min_element(stats_.begin(), stats_.end(), LessForEncodedSize);
|
frame_it = std::min_element(stats_.begin(), stats_.end(), LessForEncodedSize);
|
||||||
printf(" Min : %7" PRIuS " bytes (frame %d)\n",
|
printf(" Min : %7" PRIuS " bytes (frame %d)\n",
|
||||||
frame->encoded_frame_length_in_bytes, frame->frame_number);
|
frame_it->encoded_frame_size_bytes, frame_it->frame_number);
|
||||||
frame = std::max_element(stats_.begin(), stats_.end(), LessForEncodedSize);
|
frame_it = std::max_element(stats_.begin(), stats_.end(), LessForEncodedSize);
|
||||||
printf(" Max : %7" PRIuS " bytes (frame %d)\n",
|
printf(" Max : %7" PRIuS " bytes (frame %d)\n",
|
||||||
frame->encoded_frame_length_in_bytes, frame->frame_number);
|
frame_it->encoded_frame_size_bytes, frame_it->frame_number);
|
||||||
printf(" Average : %7" PRIuS " bytes\n",
|
printf(" Average : %7" PRIuS " bytes\n",
|
||||||
total_encoded_frames_lengths / stats_.size());
|
total_encoded_frame_size_bytes / stats_.size());
|
||||||
if (num_keyframes > 0) {
|
if (num_key_frames > 0) {
|
||||||
printf(" Average key frame size : %7" PRIuS " bytes (%" PRIuS
|
printf(" Average key frame size : %7" PRIuS " bytes (%" PRIuS
|
||||||
" keyframes)\n",
|
" keyframes)\n",
|
||||||
total_encoded_key_frames_lengths / num_keyframes, num_keyframes);
|
total_encoded_key_frame_size_bytes / num_key_frames, num_key_frames);
|
||||||
}
|
}
|
||||||
if (num_nonkeyframes > 0) {
|
if (num_delta_frames > 0) {
|
||||||
printf(" Average non-key frame size: %7" PRIuS " bytes (%" PRIuS
|
printf(" Average non-key frame size: %7" PRIuS " bytes (%" PRIuS
|
||||||
" frames)\n",
|
" frames)\n",
|
||||||
total_encoded_nonkey_frames_lengths / num_nonkeyframes,
|
total_encoded_delta_frame_size_bytes / num_delta_frames,
|
||||||
num_nonkeyframes);
|
num_delta_frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bitrate stats.
|
// Bitrate stats.
|
||||||
printf("Bitrates:\n");
|
printf("Bitrates:\n");
|
||||||
frame = std::min_element(stats_.begin(), stats_.end(), LessForBitRate);
|
frame_it = std::min_element(stats_.begin(), stats_.end(), LessForBitRate);
|
||||||
printf(" Min bitrate: %7d kbps (frame %d)\n", frame->bit_rate_in_kbps,
|
printf(" Min bitrate: %7d kbps (frame %d)\n", frame_it->bitrate_kbps,
|
||||||
frame->frame_number);
|
frame_it->frame_number);
|
||||||
frame = std::max_element(stats_.begin(), stats_.end(), LessForBitRate);
|
frame_it = std::max_element(stats_.begin(), stats_.end(), LessForBitRate);
|
||||||
printf(" Max bitrate: %7d kbps (frame %d)\n", frame->bit_rate_in_kbps,
|
printf(" Max bitrate: %7d kbps (frame %d)\n", frame_it->bitrate_kbps,
|
||||||
frame->frame_number);
|
frame_it->frame_number);
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("Total encoding time : %7d ms.\n", total_encoding_time_in_us / 1000);
|
printf("Total encoding time : %7d ms.\n", total_encoding_time_us / 1000);
|
||||||
printf("Total decoding time : %7d ms.\n", total_decoding_time_in_us / 1000);
|
printf("Total decoding time : %7d ms.\n", total_decoding_time_us / 1000);
|
||||||
printf("Total processing time: %7d ms.\n",
|
printf("Total processing time: %7d ms.\n",
|
||||||
(total_encoding_time_in_us + total_decoding_time_in_us) / 1000);
|
(total_encoding_time_us + total_decoding_time_us) / 1000);
|
||||||
|
|
||||||
|
// QP stats.
|
||||||
|
int total_qp = 0;
|
||||||
|
int total_qp_count = 0;
|
||||||
|
for (const FrameStatistic& stat : stats_) {
|
||||||
|
if (stat.qp >= 0) {
|
||||||
|
total_qp += stat.qp;
|
||||||
|
++total_qp_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
int avg_qp = (total_qp_count > 0) ? (total_qp / total_qp_count) : -1;
|
int avg_qp = (total_qp_count > 0) ? (total_qp / total_qp_count) : -1;
|
||||||
printf("Average QP: %d\n", avg_qp);
|
printf("Average QP: %d\n", avg_qp);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,48 +18,55 @@
|
|||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
// Contains statistics of a single processed frame.
|
// Statistics for one processed frame.
|
||||||
struct FrameStatistic {
|
struct FrameStatistic {
|
||||||
bool encoding_successful = false;
|
explicit FrameStatistic(int frame_number) : frame_number(frame_number) {}
|
||||||
bool decoding_successful = false;
|
const int frame_number = 0;
|
||||||
|
|
||||||
|
// Encoding.
|
||||||
|
int64_t encode_start_ns = 0;
|
||||||
int encode_return_code = 0;
|
int encode_return_code = 0;
|
||||||
|
bool encoding_successful = false;
|
||||||
|
int encode_time_us = 0;
|
||||||
|
int bitrate_kbps = 0;
|
||||||
|
size_t encoded_frame_size_bytes = 0;
|
||||||
|
webrtc::FrameType frame_type = kVideoFrameDelta;
|
||||||
|
|
||||||
|
// Decoding.
|
||||||
|
int64_t decode_start_ns = 0;
|
||||||
int decode_return_code = 0;
|
int decode_return_code = 0;
|
||||||
int encode_time_in_us = 0;
|
bool decoding_successful = false;
|
||||||
int decode_time_in_us = 0;
|
int decode_time_us = 0;
|
||||||
|
int decoded_width = 0;
|
||||||
|
int decoded_height = 0;
|
||||||
|
|
||||||
|
// Quantization.
|
||||||
int qp = -1;
|
int qp = -1;
|
||||||
int frame_number = 0;
|
|
||||||
// How many packets were discarded of the encoded frame data (if any).
|
// How many packets were discarded of the encoded frame data (if any).
|
||||||
int packets_dropped = 0;
|
int packets_dropped = 0;
|
||||||
size_t total_packets = 0;
|
size_t total_packets = 0;
|
||||||
|
size_t manipulated_length = 0;
|
||||||
// Current bit rate. Calculated out of the size divided with the time
|
|
||||||
// interval per frame.
|
|
||||||
int bit_rate_in_kbps = 0;
|
|
||||||
|
|
||||||
// Copied from EncodedImage.
|
|
||||||
size_t encoded_frame_length_in_bytes = 0;
|
|
||||||
webrtc::FrameType frame_type = kVideoFrameDelta;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles statistics from a single video processing run.
|
// Statistics for a sequence of processed frames. This class is not thread safe.
|
||||||
// Contains calculation methods for interesting metrics from these stats.
|
|
||||||
class Stats {
|
class Stats {
|
||||||
public:
|
public:
|
||||||
typedef std::vector<FrameStatistic>::iterator FrameStatisticsIterator;
|
Stats() = default;
|
||||||
|
~Stats() = default;
|
||||||
|
|
||||||
Stats();
|
// Creates a FrameStatistic for the next frame to be processed.
|
||||||
virtual ~Stats();
|
FrameStatistic* AddFrame();
|
||||||
|
|
||||||
// Add a new statistic data object.
|
// Returns the FrameStatistic corresponding to |frame_number|.
|
||||||
// The |frame_number| must be incrementing and start at zero in order to use
|
FrameStatistic* GetFrame(int frame_number);
|
||||||
// it as an index for the FrameStatistic vector.
|
|
||||||
// Returns the newly created statistic object.
|
|
||||||
FrameStatistic& NewFrame(int frame_number);
|
|
||||||
|
|
||||||
// Prints a summary of all the statistics that have been gathered during the
|
size_t size() const;
|
||||||
// processing.
|
|
||||||
void PrintSummary();
|
|
||||||
|
|
||||||
|
// TODO(brandtr): Add output as CSV.
|
||||||
|
void PrintSummary() const;
|
||||||
|
|
||||||
|
private:
|
||||||
std::vector<FrameStatistic> stats_;
|
std::vector<FrameStatistic> stats_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -11,36 +11,32 @@
|
|||||||
#include "webrtc/modules/video_coding/codecs/test/stats.h"
|
#include "webrtc/modules/video_coding/codecs/test/stats.h"
|
||||||
|
|
||||||
#include "webrtc/test/gtest.h"
|
#include "webrtc/test/gtest.h"
|
||||||
#include "webrtc/typedefs.h"
|
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
TEST(StatsTest, TestEmptyObject) {
|
TEST(StatsTest, TestEmptyObject) {
|
||||||
Stats stats;
|
Stats stats;
|
||||||
EXPECT_EQ(0u, stats.stats_.size());
|
stats.PrintSummary(); // Should not crash.
|
||||||
stats.PrintSummary(); // should not crash
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StatsTest, AddSingleFrame) {
|
TEST(StatsTest, AddSingleFrame) {
|
||||||
const int kFrameNumber = 0;
|
|
||||||
Stats stats;
|
Stats stats;
|
||||||
stats.NewFrame(kFrameNumber);
|
FrameStatistic* frame_stat = stats.AddFrame();
|
||||||
EXPECT_EQ(1u, stats.stats_.size());
|
EXPECT_EQ(0, frame_stat->frame_number);
|
||||||
FrameStatistic* frame_stat = &stats.stats_[0];
|
EXPECT_EQ(1u, stats.size());
|
||||||
EXPECT_EQ(kFrameNumber, frame_stat->frame_number);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(StatsTest, AddMultipleFrames) {
|
TEST(StatsTest, AddMultipleFrames) {
|
||||||
Stats stats;
|
Stats stats;
|
||||||
const int kNumFrames = 1000;
|
const int kNumFrames = 1000;
|
||||||
for (int i = 0; i < kNumFrames; ++i) {
|
for (int i = 0; i < kNumFrames; ++i) {
|
||||||
FrameStatistic& frame_stat = stats.NewFrame(i);
|
FrameStatistic* frame_stat = stats.AddFrame();
|
||||||
EXPECT_EQ(i, frame_stat.frame_number);
|
EXPECT_EQ(i, frame_stat->frame_number);
|
||||||
}
|
}
|
||||||
EXPECT_EQ(kNumFrames, static_cast<int>(stats.stats_.size()));
|
EXPECT_EQ(kNumFrames, static_cast<int>(stats.size()));
|
||||||
|
|
||||||
stats.PrintSummary(); // should not crash
|
stats.PrintSummary(); // Should not crash.
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@ -150,6 +150,7 @@ VideoProcessor::VideoProcessor(webrtc::VideoEncoder* encoder,
|
|||||||
analysis_frame_writer_(analysis_frame_writer),
|
analysis_frame_writer_(analysis_frame_writer),
|
||||||
encoded_frame_writer_(encoded_frame_writer),
|
encoded_frame_writer_(encoded_frame_writer),
|
||||||
decoded_frame_writer_(decoded_frame_writer),
|
decoded_frame_writer_(decoded_frame_writer),
|
||||||
|
last_inputed_frame_num_(-1),
|
||||||
last_encoded_frame_num_(-1),
|
last_encoded_frame_num_(-1),
|
||||||
last_decoded_frame_num_(-1),
|
last_decoded_frame_num_(-1),
|
||||||
first_key_frame_has_been_excluded_(false),
|
first_key_frame_has_been_excluded_(false),
|
||||||
@ -162,7 +163,6 @@ VideoProcessor::VideoProcessor(webrtc::VideoEncoder* encoder,
|
|||||||
RTC_DCHECK(analysis_frame_reader);
|
RTC_DCHECK(analysis_frame_reader);
|
||||||
RTC_DCHECK(analysis_frame_writer);
|
RTC_DCHECK(analysis_frame_writer);
|
||||||
RTC_DCHECK(stats);
|
RTC_DCHECK(stats);
|
||||||
frame_infos_.reserve(analysis_frame_reader->NumberOfFrames());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoProcessor::~VideoProcessor() = default;
|
VideoProcessor::~VideoProcessor() = default;
|
||||||
@ -225,11 +225,10 @@ void VideoProcessor::Release() {
|
|||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoProcessor::ProcessFrame(int frame_number) {
|
void VideoProcessor::ProcessFrame() {
|
||||||
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
RTC_DCHECK_CALLED_SEQUENTIALLY(&sequence_checker_);
|
||||||
RTC_DCHECK_EQ(frame_number, frame_infos_.size())
|
|
||||||
<< "Must process frames in sequence.";
|
|
||||||
RTC_DCHECK(initialized_) << "VideoProcessor not initialized.";
|
RTC_DCHECK(initialized_) << "VideoProcessor not initialized.";
|
||||||
|
++last_inputed_frame_num_;
|
||||||
|
|
||||||
// Get frame from file.
|
// Get frame from file.
|
||||||
rtc::scoped_refptr<I420BufferInterface> buffer(
|
rtc::scoped_refptr<I420BufferInterface> buffer(
|
||||||
@ -238,9 +237,10 @@ void VideoProcessor::ProcessFrame(int frame_number) {
|
|||||||
// Use the frame number as the basis for timestamp to identify frames. Let the
|
// Use the frame number as the basis for timestamp to identify frames. Let the
|
||||||
// first timestamp be non-zero, to not make the IvfFileWriter believe that we
|
// first timestamp be non-zero, to not make the IvfFileWriter believe that we
|
||||||
// want to use capture timestamps in the IVF files.
|
// want to use capture timestamps in the IVF files.
|
||||||
const uint32_t rtp_timestamp = (frame_number + 1) * kRtpClockRateHz /
|
const uint32_t rtp_timestamp = (last_inputed_frame_num_ + 1) *
|
||||||
|
kRtpClockRateHz /
|
||||||
config_.codec_settings.maxFramerate;
|
config_.codec_settings.maxFramerate;
|
||||||
rtp_timestamp_to_frame_num_[rtp_timestamp] = frame_number;
|
rtp_timestamp_to_frame_num_[rtp_timestamp] = last_inputed_frame_num_;
|
||||||
const int64_t kNoRenderTime = 0;
|
const int64_t kNoRenderTime = 0;
|
||||||
VideoFrame source_frame(buffer, rtp_timestamp, kNoRenderTime,
|
VideoFrame source_frame(buffer, rtp_timestamp, kNoRenderTime,
|
||||||
webrtc::kVideoRotation_0);
|
webrtc::kVideoRotation_0);
|
||||||
@ -248,25 +248,21 @@ void VideoProcessor::ProcessFrame(int frame_number) {
|
|||||||
// Decide if we are going to force a keyframe.
|
// Decide if we are going to force a keyframe.
|
||||||
std::vector<FrameType> frame_types(1, kVideoFrameDelta);
|
std::vector<FrameType> frame_types(1, kVideoFrameDelta);
|
||||||
if (config_.keyframe_interval > 0 &&
|
if (config_.keyframe_interval > 0 &&
|
||||||
frame_number % config_.keyframe_interval == 0) {
|
last_inputed_frame_num_ % config_.keyframe_interval == 0) {
|
||||||
frame_types[0] = kVideoFrameKey;
|
frame_types[0] = kVideoFrameKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store frame information during the different stages of encode and decode.
|
|
||||||
frame_infos_.emplace_back();
|
|
||||||
FrameInfo* frame_info = &frame_infos_.back();
|
|
||||||
|
|
||||||
// Create frame statistics object used for aggregation at end of test run.
|
// Create frame statistics object used for aggregation at end of test run.
|
||||||
FrameStatistic* frame_stat = &stats_->NewFrame(frame_number);
|
FrameStatistic* frame_stat = stats_->AddFrame();
|
||||||
|
|
||||||
// For the highest measurement accuracy of the encode time, the start/stop
|
// For the highest measurement accuracy of the encode time, the start/stop
|
||||||
// time recordings should wrap the Encode call as tightly as possible.
|
// time recordings should wrap the Encode call as tightly as possible.
|
||||||
frame_info->encode_start_ns = rtc::TimeNanos();
|
frame_stat->encode_start_ns = rtc::TimeNanos();
|
||||||
frame_stat->encode_return_code =
|
frame_stat->encode_return_code =
|
||||||
encoder_->Encode(source_frame, nullptr, &frame_types);
|
encoder_->Encode(source_frame, nullptr, &frame_types);
|
||||||
|
|
||||||
if (frame_stat->encode_return_code != WEBRTC_VIDEO_CODEC_OK) {
|
if (frame_stat->encode_return_code != WEBRTC_VIDEO_CODEC_OK) {
|
||||||
LOG(LS_WARNING) << "Failed to encode frame " << frame_number
|
LOG(LS_WARNING) << "Failed to encode frame " << last_inputed_frame_num_
|
||||||
<< ", return code: " << frame_stat->encode_return_code
|
<< ", return code: " << frame_stat->encode_return_code
|
||||||
<< ".";
|
<< ".";
|
||||||
}
|
}
|
||||||
@ -303,9 +299,8 @@ void VideoProcessor::FrameEncoded(webrtc::VideoCodecType codec,
|
|||||||
// time recordings should wrap the Encode call as tightly as possible.
|
// time recordings should wrap the Encode call as tightly as possible.
|
||||||
int64_t encode_stop_ns = rtc::TimeNanos();
|
int64_t encode_stop_ns = rtc::TimeNanos();
|
||||||
|
|
||||||
if (encoded_frame_writer_) {
|
// Take the opportunity to verify the QP bitstream parser.
|
||||||
RTC_CHECK(encoded_frame_writer_->WriteFrame(encoded_image, codec));
|
VerifyQpParser(encoded_image, config_);
|
||||||
}
|
|
||||||
|
|
||||||
// Check for dropped frames.
|
// Check for dropped frames.
|
||||||
const int frame_number =
|
const int frame_number =
|
||||||
@ -334,26 +329,23 @@ void VideoProcessor::FrameEncoded(webrtc::VideoCodecType codec,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_frame_missing =
|
const FrameStatistic* last_encoded_frame_stat =
|
||||||
(frame_infos_[last_encoded_frame_num_].manipulated_length == 0);
|
stats_->GetFrame(last_encoded_frame_num_);
|
||||||
|
last_frame_missing = (last_encoded_frame_stat->manipulated_length == 0);
|
||||||
}
|
}
|
||||||
// Ensure strict monotonicity.
|
// Ensure strict monotonicity.
|
||||||
RTC_CHECK_GT(frame_number, last_encoded_frame_num_);
|
RTC_CHECK_GT(frame_number, last_encoded_frame_num_);
|
||||||
last_encoded_frame_num_ = frame_number;
|
last_encoded_frame_num_ = frame_number;
|
||||||
|
|
||||||
// Update frame information and statistics.
|
// Update frame statistics.
|
||||||
VerifyQpParser(encoded_image, config_);
|
FrameStatistic* frame_stat = stats_->GetFrame(frame_number);
|
||||||
RTC_CHECK_LT(frame_number, frame_infos_.size());
|
frame_stat->encode_time_us =
|
||||||
FrameInfo* frame_info = &frame_infos_[frame_number];
|
GetElapsedTimeMicroseconds(frame_stat->encode_start_ns, encode_stop_ns);
|
||||||
FrameStatistic* frame_stat = &stats_->stats_[frame_number];
|
|
||||||
frame_stat->encode_time_in_us =
|
|
||||||
GetElapsedTimeMicroseconds(frame_info->encode_start_ns, encode_stop_ns);
|
|
||||||
frame_stat->encoding_successful = true;
|
frame_stat->encoding_successful = true;
|
||||||
frame_stat->encoded_frame_length_in_bytes = encoded_image._length;
|
frame_stat->encoded_frame_size_bytes = encoded_image._length;
|
||||||
frame_stat->frame_number = frame_number;
|
|
||||||
frame_stat->frame_type = encoded_image._frameType;
|
frame_stat->frame_type = encoded_image._frameType;
|
||||||
frame_stat->qp = encoded_image.qp_;
|
frame_stat->qp = encoded_image.qp_;
|
||||||
frame_stat->bit_rate_in_kbps = static_cast<int>(
|
frame_stat->bitrate_kbps = static_cast<int>(
|
||||||
encoded_image._length * config_.codec_settings.maxFramerate * 8 / 1000);
|
encoded_image._length * config_.codec_settings.maxFramerate * 8 / 1000);
|
||||||
frame_stat->total_packets =
|
frame_stat->total_packets =
|
||||||
encoded_image._length / config_.networking_config.packet_size_in_bytes +
|
encoded_image._length / config_.networking_config.packet_size_in_bytes +
|
||||||
@ -393,11 +385,11 @@ void VideoProcessor::FrameEncoded(webrtc::VideoCodecType codec,
|
|||||||
frame_stat->packets_dropped =
|
frame_stat->packets_dropped =
|
||||||
packet_manipulator_->ManipulatePackets(&copied_image);
|
packet_manipulator_->ManipulatePackets(&copied_image);
|
||||||
}
|
}
|
||||||
frame_info->manipulated_length = copied_image._length;
|
frame_stat->manipulated_length = copied_image._length;
|
||||||
|
|
||||||
// For the highest measurement accuracy of the decode time, the start/stop
|
// For the highest measurement accuracy of the decode time, the start/stop
|
||||||
// time recordings should wrap the Decode call as tightly as possible.
|
// time recordings should wrap the Decode call as tightly as possible.
|
||||||
frame_info->decode_start_ns = rtc::TimeNanos();
|
frame_stat->decode_start_ns = rtc::TimeNanos();
|
||||||
frame_stat->decode_return_code =
|
frame_stat->decode_return_code =
|
||||||
decoder_->Decode(copied_image, last_frame_missing, nullptr);
|
decoder_->Decode(copied_image, last_frame_missing, nullptr);
|
||||||
|
|
||||||
@ -415,6 +407,10 @@ void VideoProcessor::FrameEncoded(webrtc::VideoCodecType codec,
|
|||||||
decoded_frame_writer_->WriteFrame(last_decoded_frame_buffer_.data()));
|
decoded_frame_writer_->WriteFrame(last_decoded_frame_buffer_.data()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (encoded_frame_writer_) {
|
||||||
|
RTC_CHECK(encoded_frame_writer_->WriteFrame(encoded_image, codec));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoProcessor::FrameDecoded(const VideoFrame& image) {
|
void VideoProcessor::FrameDecoded(const VideoFrame& image) {
|
||||||
@ -424,26 +420,24 @@ void VideoProcessor::FrameDecoded(const VideoFrame& image) {
|
|||||||
// time recordings should wrap the Decode call as tightly as possible.
|
// time recordings should wrap the Decode call as tightly as possible.
|
||||||
int64_t decode_stop_ns = rtc::TimeNanos();
|
int64_t decode_stop_ns = rtc::TimeNanos();
|
||||||
|
|
||||||
// Update frame information and statistics.
|
// Update frame statistics.
|
||||||
const int frame_number = rtp_timestamp_to_frame_num_[image.timestamp()];
|
const int frame_number = rtp_timestamp_to_frame_num_[image.timestamp()];
|
||||||
RTC_CHECK_LT(frame_number, frame_infos_.size());
|
FrameStatistic* frame_stat = stats_->GetFrame(frame_number);
|
||||||
FrameInfo* frame_info = &frame_infos_[frame_number];
|
frame_stat->decoded_width = image.width();
|
||||||
frame_info->decoded_width = image.width();
|
frame_stat->decoded_height = image.height();
|
||||||
frame_info->decoded_height = image.height();
|
frame_stat->decode_time_us =
|
||||||
FrameStatistic* frame_stat = &stats_->stats_[frame_number];
|
GetElapsedTimeMicroseconds(frame_stat->decode_start_ns, decode_stop_ns);
|
||||||
frame_stat->decode_time_in_us =
|
|
||||||
GetElapsedTimeMicroseconds(frame_info->decode_start_ns, decode_stop_ns);
|
|
||||||
frame_stat->decoding_successful = true;
|
frame_stat->decoding_successful = true;
|
||||||
|
|
||||||
// Check if the codecs have resized the frame since previously decoded frame.
|
// Check if the codecs have resized the frame since previously decoded frame.
|
||||||
if (frame_number > 0) {
|
if (frame_number > 0) {
|
||||||
RTC_CHECK_GE(last_decoded_frame_num_, 0);
|
RTC_CHECK_GE(last_decoded_frame_num_, 0);
|
||||||
const FrameInfo& last_decoded_frame_info =
|
const FrameStatistic* last_decoded_frame_stat =
|
||||||
frame_infos_[last_decoded_frame_num_];
|
stats_->GetFrame(last_decoded_frame_num_);
|
||||||
if (static_cast<int>(image.width()) !=
|
if (static_cast<int>(image.width()) !=
|
||||||
last_decoded_frame_info.decoded_width ||
|
last_decoded_frame_stat->decoded_width ||
|
||||||
static_cast<int>(image.height()) !=
|
static_cast<int>(image.height()) !=
|
||||||
last_decoded_frame_info.decoded_height) {
|
last_decoded_frame_stat->decoded_height) {
|
||||||
RTC_CHECK_GE(rate_update_index_, 0);
|
RTC_CHECK_GE(rate_update_index_, 0);
|
||||||
++num_spatial_resizes_[rate_update_index_];
|
++num_spatial_resizes_[rate_update_index_];
|
||||||
}
|
}
|
||||||
@ -452,7 +446,7 @@ void VideoProcessor::FrameDecoded(const VideoFrame& image) {
|
|||||||
RTC_CHECK_GT(frame_number, last_decoded_frame_num_);
|
RTC_CHECK_GT(frame_number, last_decoded_frame_num_);
|
||||||
last_decoded_frame_num_ = frame_number;
|
last_decoded_frame_num_ = frame_number;
|
||||||
|
|
||||||
// Check if codec size is different from the original size, and if so,
|
// Check if frame size is different from the original size, and if so,
|
||||||
// scale back to original size. This is needed for the PSNR and SSIM
|
// scale back to original size. This is needed for the PSNR and SSIM
|
||||||
// calculations.
|
// calculations.
|
||||||
size_t extracted_length;
|
size_t extracted_length;
|
||||||
|
|||||||
@ -134,9 +134,11 @@ class VideoProcessor {
|
|||||||
// Tears down callbacks and releases the encoder and decoder.
|
// Tears down callbacks and releases the encoder and decoder.
|
||||||
void Release();
|
void Release();
|
||||||
|
|
||||||
// Processes a single frame. The frames must be processed in order, and the
|
// Reads a frame from the analysis frame reader and sends it to the encoder.
|
||||||
// VideoProcessor must be initialized first.
|
// When the encode callback is received, the encoded frame is sent to the
|
||||||
void ProcessFrame(int frame_number);
|
// decoder. The decoded frame is written to disk by the analysis frame writer.
|
||||||
|
// Objective video quality metrics can thus be calculated after the fact.
|
||||||
|
void ProcessFrame();
|
||||||
|
|
||||||
// Updates the encoder with target rates. Must be called at least once.
|
// Updates the encoder with target rates. Must be called at least once.
|
||||||
void SetRates(int bitrate_kbps, int framerate_fps);
|
void SetRates(int bitrate_kbps, int framerate_fps);
|
||||||
@ -148,18 +150,6 @@ class VideoProcessor {
|
|||||||
std::vector<int> NumberSpatialResizesPerRateUpdate() const;
|
std::vector<int> NumberSpatialResizesPerRateUpdate() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Container that holds per-frame information that needs to be stored between
|
|
||||||
// calls to Encode and Decode, as well as the corresponding callbacks. It is
|
|
||||||
// not directly used for statistics -- for that, test::FrameStatistic is used.
|
|
||||||
// TODO(brandtr): Get rid of this struct and use the Stats class instead.
|
|
||||||
struct FrameInfo {
|
|
||||||
int64_t encode_start_ns = 0;
|
|
||||||
int64_t decode_start_ns = 0;
|
|
||||||
int decoded_width = 0;
|
|
||||||
int decoded_height = 0;
|
|
||||||
size_t manipulated_length = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class VideoProcessorEncodeCompleteCallback
|
class VideoProcessorEncodeCompleteCallback
|
||||||
: public webrtc::EncodedImageCallback {
|
: public webrtc::EncodedImageCallback {
|
||||||
public:
|
public:
|
||||||
@ -283,10 +273,8 @@ class VideoProcessor {
|
|||||||
IvfFileWriter* const encoded_frame_writer_;
|
IvfFileWriter* const encoded_frame_writer_;
|
||||||
FrameWriter* const decoded_frame_writer_;
|
FrameWriter* const decoded_frame_writer_;
|
||||||
|
|
||||||
// Frame metadata for all frames that have been added through a call to
|
// Keep track of inputed/encoded/decoded frames, so we can detect frame drops.
|
||||||
// ProcessFrames(). We need to store this metadata over the course of the
|
int last_inputed_frame_num_ GUARDED_BY(sequence_checker_);
|
||||||
// test run, to support pipelining HW codecs.
|
|
||||||
std::vector<FrameInfo> frame_infos_ GUARDED_BY(sequence_checker_);
|
|
||||||
int last_encoded_frame_num_ GUARDED_BY(sequence_checker_);
|
int last_encoded_frame_num_ GUARDED_BY(sequence_checker_);
|
||||||
int last_decoded_frame_num_ GUARDED_BY(sequence_checker_);
|
int last_decoded_frame_num_ GUARDED_BY(sequence_checker_);
|
||||||
|
|
||||||
|
|||||||
@ -191,8 +191,7 @@ void VideoProcessorIntegrationTest::ProcessFramesAndMaybeVerify(
|
|||||||
rate_profile.input_frame_rate[rate_update_index]);
|
rate_profile.input_frame_rate[rate_update_index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
task_queue.PostTask(
|
task_queue.PostTask([this] { processor_->ProcessFrame(); });
|
||||||
[this, frame_number] { processor_->ProcessFrame(frame_number); });
|
|
||||||
++frame_number;
|
++frame_number;
|
||||||
|
|
||||||
if (frame_number ==
|
if (frame_number ==
|
||||||
@ -245,7 +244,7 @@ void VideoProcessorIntegrationTest::ProcessFramesAndMaybeVerify(
|
|||||||
num_dropped_frames, num_resize_actions);
|
num_dropped_frames, num_resize_actions);
|
||||||
|
|
||||||
// Calculate and print other statistics.
|
// Calculate and print other statistics.
|
||||||
EXPECT_EQ(num_frames, static_cast<int>(stats_.stats_.size()));
|
EXPECT_EQ(num_frames, static_cast<int>(stats_.size()));
|
||||||
stats_.PrintSummary();
|
stats_.PrintSummary();
|
||||||
|
|
||||||
// Calculate and print image quality statistics.
|
// Calculate and print image quality statistics.
|
||||||
@ -414,10 +413,10 @@ void VideoProcessorIntegrationTest::UpdateRateControlMetrics(int frame_number) {
|
|||||||
++num_frames_per_update_[tl_idx];
|
++num_frames_per_update_[tl_idx];
|
||||||
++num_frames_total_;
|
++num_frames_total_;
|
||||||
|
|
||||||
FrameType frame_type = stats_.stats_[frame_number].frame_type;
|
const FrameStatistic* frame_stat = stats_.GetFrame(frame_number);
|
||||||
|
FrameType frame_type = frame_stat->frame_type;
|
||||||
float encoded_size_kbits =
|
float encoded_size_kbits =
|
||||||
stats_.stats_[frame_number].encoded_frame_length_in_bytes * 8.0f /
|
frame_stat->encoded_frame_size_bytes * 8.0f / 1000.0f;
|
||||||
1000.0f;
|
|
||||||
|
|
||||||
// Update layer data.
|
// Update layer data.
|
||||||
// Update rate mismatch relative to per-frame bandwidth for delta frames.
|
// Update rate mismatch relative to per-frame bandwidth for delta frames.
|
||||||
|
|||||||
@ -109,13 +109,13 @@ TEST_F(VideoProcessorTest, ProcessFrames_FixedFramerate) {
|
|||||||
encoder_mock_,
|
encoder_mock_,
|
||||||
Encode(Property(&VideoFrame::timestamp, 1 * 90000 / kFramerateFps), _, _))
|
Encode(Property(&VideoFrame::timestamp, 1 * 90000 / kFramerateFps), _, _))
|
||||||
.Times(1);
|
.Times(1);
|
||||||
video_processor_->ProcessFrame(0);
|
video_processor_->ProcessFrame();
|
||||||
|
|
||||||
EXPECT_CALL(
|
EXPECT_CALL(
|
||||||
encoder_mock_,
|
encoder_mock_,
|
||||||
Encode(Property(&VideoFrame::timestamp, 2 * 90000 / kFramerateFps), _, _))
|
Encode(Property(&VideoFrame::timestamp, 2 * 90000 / kFramerateFps), _, _))
|
||||||
.Times(1);
|
.Times(1);
|
||||||
video_processor_->ProcessFrame(1);
|
video_processor_->ProcessFrame();
|
||||||
|
|
||||||
ExpectRelease();
|
ExpectRelease();
|
||||||
video_processor_->Release();
|
video_processor_->Release();
|
||||||
@ -135,7 +135,7 @@ TEST_F(VideoProcessorTest, ProcessFrames_VariableFramerate) {
|
|||||||
1 * 90000 / kStartFramerateFps),
|
1 * 90000 / kStartFramerateFps),
|
||||||
_, _))
|
_, _))
|
||||||
.Times(1);
|
.Times(1);
|
||||||
video_processor_->ProcessFrame(0);
|
video_processor_->ProcessFrame();
|
||||||
|
|
||||||
const int kNewFramerateFps = 13;
|
const int kNewFramerateFps = 13;
|
||||||
video_processor_->SetRates(kBitrateKbps, kNewFramerateFps);
|
video_processor_->SetRates(kBitrateKbps, kNewFramerateFps);
|
||||||
@ -144,7 +144,7 @@ TEST_F(VideoProcessorTest, ProcessFrames_VariableFramerate) {
|
|||||||
2 * 90000 / kNewFramerateFps),
|
2 * 90000 / kNewFramerateFps),
|
||||||
_, _))
|
_, _))
|
||||||
.Times(1);
|
.Times(1);
|
||||||
video_processor_->ProcessFrame(1);
|
video_processor_->ProcessFrame();
|
||||||
|
|
||||||
ExpectRelease();
|
ExpectRelease();
|
||||||
video_processor_->Release();
|
video_processor_->Release();
|
||||||
|
|||||||
Reference in New Issue
Block a user