mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-14 14:20:45 +01:00

Add functionality for dumping encoder and decoder input to file in video codec test. Bug: b/261160916, webrtc:14852 Change-Id: I49a84a886d87903c601cf5c35bd723b6393c2a75 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/298051 Commit-Queue: Sergey Silkin <ssilkin@webrtc.org> Reviewed-by: Erik Språng <sprang@webrtc.org> Cr-Commit-Position: refs/heads/main@{#39626}
437 lines
13 KiB
C++
437 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2022 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 "modules/video_coding/codecs/test/video_codec_tester_impl.h"
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "api/task_queue/default_task_queue_factory.h"
|
|
#include "api/units/frequency.h"
|
|
#include "api/units/time_delta.h"
|
|
#include "api/units/timestamp.h"
|
|
#include "api/video/encoded_image.h"
|
|
#include "api/video/i420_buffer.h"
|
|
#include "api/video/video_codec_type.h"
|
|
#include "api/video/video_frame.h"
|
|
#include "modules/video_coding/codecs/test/video_codec_analyzer.h"
|
|
#include "modules/video_coding/utility/ivf_file_writer.h"
|
|
#include "rtc_base/event.h"
|
|
#include "rtc_base/time_utils.h"
|
|
#include "system_wrappers/include/sleep.h"
|
|
#include "test/testsupport/video_frame_writer.h"
|
|
|
|
namespace webrtc {
|
|
namespace test {
|
|
|
|
namespace {
|
|
using RawVideoSource = VideoCodecTester::RawVideoSource;
|
|
using CodedVideoSource = VideoCodecTester::CodedVideoSource;
|
|
using Decoder = VideoCodecTester::Decoder;
|
|
using Encoder = VideoCodecTester::Encoder;
|
|
using EncoderSettings = VideoCodecTester::EncoderSettings;
|
|
using DecoderSettings = VideoCodecTester::DecoderSettings;
|
|
using PacingSettings = VideoCodecTester::PacingSettings;
|
|
using PacingMode = PacingSettings::PacingMode;
|
|
|
|
constexpr Frequency k90kHz = Frequency::Hertz(90000);
|
|
|
|
// A thread-safe wrapper for video source to be shared with the quality analyzer
|
|
// that reads reference frames from a separate thread.
|
|
class SyncRawVideoSource : public VideoCodecAnalyzer::ReferenceVideoSource {
|
|
public:
|
|
explicit SyncRawVideoSource(RawVideoSource* video_source)
|
|
: video_source_(video_source) {}
|
|
|
|
absl::optional<VideoFrame> PullFrame() {
|
|
MutexLock lock(&mutex_);
|
|
return video_source_->PullFrame();
|
|
}
|
|
|
|
VideoFrame GetFrame(uint32_t timestamp_rtp, Resolution resolution) override {
|
|
MutexLock lock(&mutex_);
|
|
return video_source_->GetFrame(timestamp_rtp, resolution);
|
|
}
|
|
|
|
protected:
|
|
RawVideoSource* const video_source_ RTC_GUARDED_BY(mutex_);
|
|
Mutex mutex_;
|
|
};
|
|
|
|
// Pacer calculates delay necessary to keep frame encode or decode call spaced
|
|
// from the previous calls by the pacing time. `Delay` is expected to be called
|
|
// as close as possible to posting frame encode or decode task. This class is
|
|
// not thread safe.
|
|
class Pacer {
|
|
public:
|
|
explicit Pacer(PacingSettings settings)
|
|
: settings_(settings), delay_(TimeDelta::Zero()) {}
|
|
Timestamp Schedule(Timestamp timestamp) {
|
|
Timestamp now = Timestamp::Micros(rtc::TimeMicros());
|
|
if (settings_.mode == PacingMode::kNoPacing) {
|
|
return now;
|
|
}
|
|
|
|
Timestamp scheduled = now;
|
|
if (prev_scheduled_) {
|
|
scheduled = *prev_scheduled_ + PacingTime(timestamp);
|
|
if (scheduled < now) {
|
|
scheduled = now;
|
|
}
|
|
}
|
|
|
|
prev_timestamp_ = timestamp;
|
|
prev_scheduled_ = scheduled;
|
|
return scheduled;
|
|
}
|
|
|
|
private:
|
|
TimeDelta PacingTime(Timestamp timestamp) {
|
|
if (settings_.mode == PacingMode::kRealTime) {
|
|
return timestamp - *prev_timestamp_;
|
|
}
|
|
RTC_CHECK_EQ(PacingMode::kConstantRate, settings_.mode);
|
|
return 1 / settings_.constant_rate;
|
|
}
|
|
|
|
PacingSettings settings_;
|
|
absl::optional<Timestamp> prev_timestamp_;
|
|
absl::optional<Timestamp> prev_scheduled_;
|
|
TimeDelta delay_;
|
|
};
|
|
|
|
// Task queue that keeps the number of queued tasks below a certain limit. If
|
|
// the limit is reached, posting of a next task is blocked until execution of a
|
|
// previously posted task starts. This class is not thread-safe.
|
|
class LimitedTaskQueue {
|
|
public:
|
|
// The codec tester reads frames from video source in the main thread.
|
|
// Encoding and decoding are done in separate threads. If encoding or
|
|
// decoding is slow, the reading may go far ahead and may buffer too many
|
|
// frames in memory. To prevent this we limit the encoding/decoding queue
|
|
// size. When the queue is full, the main thread and, hence, reading frames
|
|
// from video source is blocked until a previously posted encoding/decoding
|
|
// task starts.
|
|
static constexpr int kMaxTaskQueueSize = 3;
|
|
|
|
LimitedTaskQueue() : queue_size_(0) {}
|
|
|
|
void PostScheduledTask(absl::AnyInvocable<void() &&> task, Timestamp start) {
|
|
++queue_size_;
|
|
task_queue_.PostTask([this, task = std::move(task), start]() mutable {
|
|
int wait_ms = static_cast<int>(start.ms() - rtc::TimeMillis());
|
|
if (wait_ms > 0) {
|
|
SleepMs(wait_ms);
|
|
}
|
|
|
|
std::move(task)();
|
|
--queue_size_;
|
|
task_executed_.Set();
|
|
});
|
|
|
|
task_executed_.Reset();
|
|
if (queue_size_ > kMaxTaskQueueSize) {
|
|
task_executed_.Wait(rtc::Event::kForever);
|
|
}
|
|
RTC_CHECK(queue_size_ <= kMaxTaskQueueSize);
|
|
}
|
|
|
|
void WaitForPreviouslyPostedTasks() {
|
|
task_queue_.SendTask([] {});
|
|
}
|
|
|
|
TaskQueueForTest task_queue_;
|
|
std::atomic_int queue_size_;
|
|
rtc::Event task_executed_;
|
|
};
|
|
|
|
class TesterY4mWriter {
|
|
public:
|
|
explicit TesterY4mWriter(absl::string_view base_path)
|
|
: base_path_(base_path) {}
|
|
|
|
~TesterY4mWriter() {
|
|
task_queue_.SendTask([] {});
|
|
}
|
|
|
|
void Write(const VideoFrame& frame, int spatial_idx) {
|
|
task_queue_.PostTask([this, frame, spatial_idx] {
|
|
if (y4m_writers_.find(spatial_idx) == y4m_writers_.end()) {
|
|
std::string file_path =
|
|
base_path_ + "_s" + std::to_string(spatial_idx) + ".y4m";
|
|
|
|
Y4mVideoFrameWriterImpl* y4m_writer = new Y4mVideoFrameWriterImpl(
|
|
file_path, frame.width(), frame.height(), /*fps=*/30);
|
|
RTC_CHECK(y4m_writer);
|
|
|
|
y4m_writers_[spatial_idx] =
|
|
std::unique_ptr<VideoFrameWriter>(y4m_writer);
|
|
}
|
|
|
|
y4m_writers_.at(spatial_idx)->WriteFrame(frame);
|
|
});
|
|
}
|
|
|
|
protected:
|
|
std::string base_path_;
|
|
std::map<int, std::unique_ptr<VideoFrameWriter>> y4m_writers_;
|
|
TaskQueueForTest task_queue_;
|
|
};
|
|
|
|
class TesterIvfWriter {
|
|
public:
|
|
explicit TesterIvfWriter(absl::string_view base_path)
|
|
: base_path_(base_path) {}
|
|
|
|
~TesterIvfWriter() {
|
|
task_queue_.SendTask([] {});
|
|
}
|
|
|
|
void Write(const EncodedImage& encoded_frame) {
|
|
task_queue_.PostTask([this, encoded_frame] {
|
|
int spatial_idx = encoded_frame.SpatialIndex().value_or(0);
|
|
if (ivf_file_writers_.find(spatial_idx) == ivf_file_writers_.end()) {
|
|
std::string ivf_path =
|
|
base_path_ + "_s" + std::to_string(spatial_idx) + ".ivf";
|
|
|
|
FileWrapper ivf_file = FileWrapper::OpenWriteOnly(ivf_path);
|
|
RTC_CHECK(ivf_file.is_open());
|
|
|
|
std::unique_ptr<IvfFileWriter> ivf_writer =
|
|
IvfFileWriter::Wrap(std::move(ivf_file), /*byte_limit=*/0);
|
|
RTC_CHECK(ivf_writer);
|
|
|
|
ivf_file_writers_[spatial_idx] = std::move(ivf_writer);
|
|
}
|
|
|
|
// To play: ffplay -vcodec vp8|vp9|av1|hevc|h264 filename
|
|
ivf_file_writers_.at(spatial_idx)
|
|
->WriteFrame(encoded_frame, VideoCodecType::kVideoCodecGeneric);
|
|
});
|
|
}
|
|
|
|
protected:
|
|
std::string base_path_;
|
|
std::map<int, std::unique_ptr<IvfFileWriter>> ivf_file_writers_;
|
|
TaskQueueForTest task_queue_;
|
|
};
|
|
|
|
class TesterDecoder {
|
|
public:
|
|
TesterDecoder(Decoder* decoder,
|
|
VideoCodecAnalyzer* analyzer,
|
|
const DecoderSettings& settings)
|
|
: decoder_(decoder),
|
|
analyzer_(analyzer),
|
|
settings_(settings),
|
|
pacer_(settings.pacing) {
|
|
RTC_CHECK(analyzer_) << "Analyzer must be provided";
|
|
|
|
if (settings.decoder_input_base_path) {
|
|
input_writer_ =
|
|
std::make_unique<TesterIvfWriter>(*settings.decoder_input_base_path);
|
|
}
|
|
|
|
if (settings.decoder_output_base_path) {
|
|
output_writer_ =
|
|
std::make_unique<TesterY4mWriter>(*settings.decoder_output_base_path);
|
|
}
|
|
}
|
|
|
|
void Initialize() {
|
|
task_queue_.PostScheduledTask([this] { decoder_->Initialize(); },
|
|
Timestamp::Zero());
|
|
task_queue_.WaitForPreviouslyPostedTasks();
|
|
}
|
|
|
|
void Decode(const EncodedImage& input_frame) {
|
|
Timestamp timestamp =
|
|
Timestamp::Micros((input_frame.Timestamp() / k90kHz).us());
|
|
|
|
task_queue_.PostScheduledTask(
|
|
[this, input_frame] {
|
|
analyzer_->StartDecode(input_frame);
|
|
|
|
decoder_->Decode(
|
|
input_frame,
|
|
[this, spatial_idx = input_frame.SpatialIndex().value_or(0)](
|
|
const VideoFrame& output_frame) {
|
|
analyzer_->FinishDecode(output_frame, spatial_idx);
|
|
|
|
if (output_writer_) {
|
|
output_writer_->Write(output_frame, spatial_idx);
|
|
}
|
|
});
|
|
|
|
if (input_writer_) {
|
|
input_writer_->Write(input_frame);
|
|
}
|
|
},
|
|
pacer_.Schedule(timestamp));
|
|
}
|
|
|
|
void Flush() {
|
|
task_queue_.PostScheduledTask([this] { decoder_->Flush(); },
|
|
Timestamp::Zero());
|
|
task_queue_.WaitForPreviouslyPostedTasks();
|
|
}
|
|
|
|
protected:
|
|
Decoder* const decoder_;
|
|
VideoCodecAnalyzer* const analyzer_;
|
|
const DecoderSettings& settings_;
|
|
Pacer pacer_;
|
|
LimitedTaskQueue task_queue_;
|
|
std::unique_ptr<TesterIvfWriter> input_writer_;
|
|
std::unique_ptr<TesterY4mWriter> output_writer_;
|
|
};
|
|
|
|
class TesterEncoder {
|
|
public:
|
|
TesterEncoder(Encoder* encoder,
|
|
TesterDecoder* decoder,
|
|
VideoCodecAnalyzer* analyzer,
|
|
const EncoderSettings& settings)
|
|
: encoder_(encoder),
|
|
decoder_(decoder),
|
|
analyzer_(analyzer),
|
|
settings_(settings),
|
|
pacer_(settings.pacing) {
|
|
RTC_CHECK(analyzer_) << "Analyzer must be provided";
|
|
if (settings.encoder_input_base_path) {
|
|
input_writer_ =
|
|
std::make_unique<TesterY4mWriter>(*settings.encoder_input_base_path);
|
|
}
|
|
|
|
if (settings.encoder_output_base_path) {
|
|
output_writer_ =
|
|
std::make_unique<TesterIvfWriter>(*settings.encoder_output_base_path);
|
|
}
|
|
}
|
|
|
|
void Initialize() {
|
|
task_queue_.PostScheduledTask([this] { encoder_->Initialize(); },
|
|
Timestamp::Zero());
|
|
task_queue_.WaitForPreviouslyPostedTasks();
|
|
}
|
|
|
|
void Encode(const VideoFrame& input_frame) {
|
|
Timestamp timestamp =
|
|
Timestamp::Micros((input_frame.timestamp() / k90kHz).us());
|
|
|
|
task_queue_.PostScheduledTask(
|
|
[this, input_frame] {
|
|
analyzer_->StartEncode(input_frame);
|
|
encoder_->Encode(input_frame,
|
|
[this](const EncodedImage& encoded_frame) {
|
|
analyzer_->FinishEncode(encoded_frame);
|
|
|
|
if (decoder_ != nullptr) {
|
|
decoder_->Decode(encoded_frame);
|
|
}
|
|
|
|
if (output_writer_ != nullptr) {
|
|
output_writer_->Write(encoded_frame);
|
|
}
|
|
});
|
|
|
|
if (input_writer_) {
|
|
input_writer_->Write(input_frame, /*spatial_idx=*/0);
|
|
}
|
|
},
|
|
pacer_.Schedule(timestamp));
|
|
}
|
|
|
|
void Flush() {
|
|
task_queue_.PostScheduledTask([this] { encoder_->Flush(); },
|
|
Timestamp::Zero());
|
|
task_queue_.WaitForPreviouslyPostedTasks();
|
|
}
|
|
|
|
protected:
|
|
Encoder* const encoder_;
|
|
TesterDecoder* const decoder_;
|
|
VideoCodecAnalyzer* const analyzer_;
|
|
const EncoderSettings& settings_;
|
|
std::unique_ptr<TesterY4mWriter> input_writer_;
|
|
std::unique_ptr<TesterIvfWriter> output_writer_;
|
|
Pacer pacer_;
|
|
LimitedTaskQueue task_queue_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<VideoCodecStats> VideoCodecTesterImpl::RunDecodeTest(
|
|
CodedVideoSource* video_source,
|
|
Decoder* decoder,
|
|
const DecoderSettings& decoder_settings) {
|
|
VideoCodecAnalyzer perf_analyzer;
|
|
TesterDecoder tester_decoder(decoder, &perf_analyzer, decoder_settings);
|
|
|
|
tester_decoder.Initialize();
|
|
|
|
while (auto frame = video_source->PullFrame()) {
|
|
tester_decoder.Decode(*frame);
|
|
}
|
|
|
|
tester_decoder.Flush();
|
|
|
|
return perf_analyzer.GetStats();
|
|
}
|
|
|
|
std::unique_ptr<VideoCodecStats> VideoCodecTesterImpl::RunEncodeTest(
|
|
RawVideoSource* video_source,
|
|
Encoder* encoder,
|
|
const EncoderSettings& encoder_settings) {
|
|
SyncRawVideoSource sync_source(video_source);
|
|
VideoCodecAnalyzer perf_analyzer;
|
|
TesterEncoder tester_encoder(encoder, /*decoder=*/nullptr, &perf_analyzer,
|
|
encoder_settings);
|
|
|
|
tester_encoder.Initialize();
|
|
|
|
while (auto frame = sync_source.PullFrame()) {
|
|
tester_encoder.Encode(*frame);
|
|
}
|
|
|
|
tester_encoder.Flush();
|
|
|
|
return perf_analyzer.GetStats();
|
|
}
|
|
|
|
std::unique_ptr<VideoCodecStats> VideoCodecTesterImpl::RunEncodeDecodeTest(
|
|
RawVideoSource* video_source,
|
|
Encoder* encoder,
|
|
Decoder* decoder,
|
|
const EncoderSettings& encoder_settings,
|
|
const DecoderSettings& decoder_settings) {
|
|
SyncRawVideoSource sync_source(video_source);
|
|
VideoCodecAnalyzer perf_analyzer(&sync_source);
|
|
TesterDecoder tester_decoder(decoder, &perf_analyzer, decoder_settings);
|
|
TesterEncoder tester_encoder(encoder, &tester_decoder, &perf_analyzer,
|
|
encoder_settings);
|
|
|
|
tester_encoder.Initialize();
|
|
tester_decoder.Initialize();
|
|
|
|
while (auto frame = sync_source.PullFrame()) {
|
|
tester_encoder.Encode(*frame);
|
|
}
|
|
|
|
tester_encoder.Flush();
|
|
tester_decoder.Flush();
|
|
|
|
return perf_analyzer.GetStats();
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace webrtc
|