Expose Ivf logging through the native API

BUG=webrtc:6300

Review-Url: https://codereview.webrtc.org/2303273002
Cr-Commit-Position: refs/heads/master@{#14419}
This commit is contained in:
palmkvist 2016-09-28 06:19:48 -07:00 committed by Commit bot
parent 242d8bdddd
commit e75f204b06
17 changed files with 449 additions and 167 deletions

View File

@ -15,6 +15,7 @@
#include "webrtc/api/call/audio_sink.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/platform_file.h"
#include "webrtc/base/gunit.h"
#include "webrtc/media/base/rtputils.h"
@ -182,6 +183,13 @@ webrtc::VideoSendStream::Stats FakeVideoSendStream::GetStats() {
return stats_;
}
void FakeVideoSendStream::EnableEncodedFrameRecording(
const std::vector<rtc::PlatformFile>& files,
size_t byte_limit) {
for (rtc::PlatformFile file : files)
rtc::ClosePlatformFile(file);
}
void FakeVideoSendStream::ReconfigureVideoEncoder(
webrtc::VideoEncoderConfig config) {
if (config.encoder_specific_settings != NULL) {
@ -258,6 +266,11 @@ void FakeVideoReceiveStream::SetStats(
stats_ = stats;
}
void FakeVideoReceiveStream::EnableEncodedFrameRecording(rtc::PlatformFile file,
size_t byte_limit) {
rtc::ClosePlatformFile(file);
}
FakeCall::FakeCall(const webrtc::Call::Config& config)
: config_(config),
audio_network_state_(webrtc::kNetworkUp),

View File

@ -21,6 +21,7 @@
#define WEBRTC_MEDIA_ENGINE_FAKEWEBRTCCALL_H_
#include <memory>
#include <string>
#include <vector>
#include "webrtc/api/call/audio_receive_stream.h"
@ -123,6 +124,9 @@ class FakeVideoSendStream final
return num_encoder_reconfigurations_;
}
void EnableEncodedFrameRecording(const std::vector<rtc::PlatformFile>& files,
size_t byte_limit) override;
private:
// rtc::VideoSinkInterface<VideoFrame> implementation.
void OnFrame(const webrtc::VideoFrame& frame) override;
@ -162,6 +166,9 @@ class FakeVideoReceiveStream final : public webrtc::VideoReceiveStream {
void SetStats(const webrtc::VideoReceiveStream::Stats& stats);
void EnableEncodedFrameRecording(rtc::PlatformFile file,
size_t byte_limit) override;
private:
// webrtc::VideoReceiveStream implementation.
void Start() override;
@ -257,4 +264,4 @@ class FakeCall final : public webrtc::Call, public webrtc::PacketReceiver {
};
} // namespace cricket
#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_UNITTEST_H_
#endif // WEBRTC_MEDIA_ENGINE_FAKEWEBRTCCALL_H_

View File

@ -10,48 +10,47 @@
#include "webrtc/modules/video_coding/utility/ivf_file_writer.h"
#include <string>
#include <utility>
#include "webrtc/base/checks.h"
#include "webrtc/base/logging.h"
#include "webrtc/modules/rtp_rtcp/source/byte_io.h"
// TODO(palmkvist): make logging more informative in the absence of a file name
// (or get one)
namespace webrtc {
IvfFileWriter::IvfFileWriter(const std::string& file_name,
std::unique_ptr<FileWrapper> file,
VideoCodecType codec_type)
: codec_type_(codec_type),
const size_t kIvfHeaderSize = 32;
IvfFileWriter::IvfFileWriter(rtc::File file, size_t byte_limit)
: codec_type_(kVideoCodecUnknown),
bytes_written_(0),
byte_limit_(byte_limit),
num_frames_(0),
width_(0),
height_(0),
last_timestamp_(-1),
using_capture_timestamps_(false),
file_name_(file_name),
file_(std::move(file)) {}
file_(std::move(file)) {
RTC_DCHECK(byte_limit == 0 || kIvfHeaderSize <= byte_limit)
<< "The byte_limit is too low, not even the header will fit.";
}
IvfFileWriter::~IvfFileWriter() {
Close();
}
const size_t kIvfHeaderSize = 32;
std::unique_ptr<IvfFileWriter> IvfFileWriter::Open(const std::string& file_name,
VideoCodecType codec_type) {
std::unique_ptr<IvfFileWriter> file_writer;
std::unique_ptr<FileWrapper> file(FileWrapper::Create());
if (!file->OpenFile(file_name.c_str(), false))
return file_writer;
file_writer.reset(new IvfFileWriter(
file_name, std::unique_ptr<FileWrapper>(std::move(file)), codec_type));
if (!file_writer->WriteHeader())
file_writer.reset();
return file_writer;
std::unique_ptr<IvfFileWriter> IvfFileWriter::Wrap(rtc::File file,
size_t byte_limit) {
return std::unique_ptr<IvfFileWriter>(
new IvfFileWriter(std::move(file), byte_limit));
}
bool IvfFileWriter::WriteHeader() {
if (file_->Rewind() != 0) {
LOG(LS_WARNING) << "Unable to rewind output file " << file_name_;
if (!file_.Seek(0)) {
LOG(LS_WARNING) << "Unable to rewind ivf output file.";
return false;
}
@ -98,21 +97,28 @@ bool IvfFileWriter::WriteHeader() {
static_cast<uint32_t>(num_frames_));
ByteWriter<uint32_t>::WriteLittleEndian(&ivf_header[28], 0); // Reserved.
if (!file_->Write(ivf_header, kIvfHeaderSize)) {
LOG(LS_ERROR) << "Unable to write IVF header for file " << file_name_;
if (file_.Write(ivf_header, kIvfHeaderSize) < kIvfHeaderSize) {
LOG(LS_ERROR) << "Unable to write IVF header for ivf output file.";
return false;
}
if (bytes_written_ < kIvfHeaderSize) {
bytes_written_ = kIvfHeaderSize;
}
return true;
}
bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image) {
bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image,
VideoCodecType codec_type) {
width_ = encoded_image._encodedWidth;
height_ = encoded_image._encodedHeight;
RTC_CHECK_GT(width_, 0);
RTC_CHECK_GT(height_, 0);
using_capture_timestamps_ = encoded_image._timeStamp == 0;
codec_type_ = codec_type;
if (!WriteHeader())
return false;
@ -130,20 +136,22 @@ bool IvfFileWriter::InitFromFirstFrame(const EncodedImage& encoded_image) {
default:
codec_name = "Unknown";
}
LOG(LS_WARNING) << "Created IVF file " << file_name_
<< " for codec data of type " << codec_name
LOG(LS_WARNING) << "Created IVF file for codec data of type " << codec_name
<< " at resolution " << width_ << " x " << height_
<< ", using " << (using_capture_timestamps_ ? "1" : "90")
<< "kHz clock resolution.";
return true;
}
bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image) {
RTC_DCHECK(file_->is_open());
if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image))
bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image,
VideoCodecType codec_type) {
if (!file_.IsOpen())
return false;
if (num_frames_ == 0 && !InitFromFirstFrame(encoded_image, codec_type))
return false;
RTC_DCHECK_EQ(codec_type_, codec_type);
if ((encoded_image._encodedWidth > 0 || encoded_image._encodedHeight > 0) &&
(encoded_image._encodedHeight != height_ ||
encoded_image._encodedWidth != width_)) {
@ -163,35 +171,41 @@ bool IvfFileWriter::WriteFrame(const EncodedImage& encoded_image) {
last_timestamp_ = timestamp;
const size_t kFrameHeaderSize = 12;
if (byte_limit_ != 0 &&
bytes_written_ + kFrameHeaderSize + encoded_image._length > byte_limit_) {
LOG(LS_WARNING) << "Closing IVF file due to reaching size limit: "
<< byte_limit_ << " bytes.";
Close();
return false;
}
uint8_t frame_header[kFrameHeaderSize] = {};
ByteWriter<uint32_t>::WriteLittleEndian(
&frame_header[0], static_cast<uint32_t>(encoded_image._length));
ByteWriter<uint64_t>::WriteLittleEndian(&frame_header[4], timestamp);
if (!file_->Write(frame_header, kFrameHeaderSize) ||
!file_->Write(encoded_image._buffer, encoded_image._length)) {
LOG(LS_ERROR) << "Unable to write frame to file " << file_name_;
if (file_.Write(frame_header, kFrameHeaderSize) < kFrameHeaderSize ||
file_.Write(encoded_image._buffer, encoded_image._length) <
encoded_image._length) {
LOG(LS_ERROR) << "Unable to write frame to file.";
return false;
}
bytes_written_ += kFrameHeaderSize + encoded_image._length;
++num_frames_;
return true;
}
bool IvfFileWriter::Close() {
if (!file_->is_open())
if (!file_.IsOpen())
return false;
if (num_frames_ == 0) {
// No frame written to file, close and remove it entirely if possible.
file_->CloseFile();
if (remove(file_name_.c_str()) != 0)
LOG(LS_WARNING) << "Failed to remove empty IVF file " << file_name_;
file_.Close();
return true;
}
bool ret = WriteHeader();
file_->CloseFile();
file_.Close();
return ret;
}

View File

@ -15,38 +15,42 @@
#include <string>
#include "webrtc/base/constructormagic.h"
#include "webrtc/base/file.h"
#include "webrtc/base/timeutils.h"
#include "webrtc/modules/include/module_common_types.h"
#include "webrtc/video_frame.h"
#include "webrtc/system_wrappers/include/file_wrapper.h"
namespace webrtc {
class IvfFileWriter {
public:
// Takes ownership of the file, which will be closed either through
// Close or ~IvfFileWriter. If writing a frame would take the file above the
// |byte_limit| the file will be closed, the write (and all future writes)
// will fail. A |byte_limit| of 0 is equivalent to no limit.
static std::unique_ptr<IvfFileWriter> Wrap(rtc::File file, size_t byte_limit);
~IvfFileWriter();
static std::unique_ptr<IvfFileWriter> Open(const std::string& file_name,
VideoCodecType codec_type);
bool WriteFrame(const EncodedImage& encoded_image);
bool WriteFrame(const EncodedImage& encoded_image, VideoCodecType codec_type);
bool Close();
private:
IvfFileWriter(const std::string& path_name,
std::unique_ptr<FileWrapper> file,
VideoCodecType codec_type);
bool WriteHeader();
bool InitFromFirstFrame(const EncodedImage& encoded_image);
explicit IvfFileWriter(rtc::File file, size_t byte_limit);
const VideoCodecType codec_type_;
bool WriteHeader();
bool InitFromFirstFrame(const EncodedImage& encoded_image,
VideoCodecType codec_type);
VideoCodecType codec_type_;
size_t bytes_written_;
size_t byte_limit_;
size_t num_frames_;
uint16_t width_;
uint16_t height_;
int64_t last_timestamp_;
bool using_capture_timestamps_;
rtc::TimestampWrapAroundHandler wrap_handler_;
const std::string file_name_;
std::unique_ptr<FileWrapper> file_;
rtc::File file_;
RTC_DISALLOW_COPY_AND_ASSIGN(IvfFileWriter);
};

View File

@ -26,23 +26,18 @@ namespace {
static const int kHeaderSize = 32;
static const int kFrameHeaderSize = 12;
static uint8_t dummy_payload[4] = {0, 1, 2, 3};
static const int kMaxFileRetries = 5;
} // namespace
class IvfFileWriterTest : public ::testing::Test {
protected:
void SetUp() override {
const uint64_t start_id = rtc::CreateRandomId64();
uint64_t id = start_id;
do {
std::ostringstream oss;
oss << test::OutputPath() << "ivf_test_file_" << id++ << ".ivf";
file_name_ = oss.str();
} while ((id - start_id) < 100u && FileExists(false));
ASSERT_LT(id - start_id, 100u);
file_name_ =
webrtc::test::TempFilename(webrtc::test::OutputPath(), "test_file");
}
void TearDown() override { rtc::RemoveFile(file_name_); }
bool WriteDummyTestFrames(int width,
bool WriteDummyTestFrames(VideoCodecType codec_type,
int width,
int height,
int num_frames,
bool use_capture_tims_ms) {
@ -57,21 +52,21 @@ class IvfFileWriterTest : public ::testing::Test {
} else {
frame._timeStamp = i;
}
if (!file_writer_->WriteFrame(frame))
if (!file_writer_->WriteFrame(frame, codec_type))
return false;
}
return true;
}
void VerifyIvfHeader(FileWrapper* file,
void VerifyIvfHeader(rtc::File* file,
const uint8_t fourcc[4],
int width,
int height,
uint32_t num_frames,
bool use_capture_tims_ms) {
ASSERT_TRUE(file->is_open());
ASSERT_TRUE(file->IsOpen());
uint8_t data[kHeaderSize];
ASSERT_EQ(kHeaderSize, file->Read(data, kHeaderSize));
ASSERT_EQ(static_cast<size_t>(kHeaderSize), file->Read(data, kHeaderSize));
uint8_t dkif[4] = {'D', 'K', 'I', 'F'};
EXPECT_EQ(0, memcmp(dkif, data, 4));
@ -87,11 +82,12 @@ class IvfFileWriterTest : public ::testing::Test {
EXPECT_EQ(0u, ByteReader<uint32_t>::ReadLittleEndian(&data[28]));
}
void VerifyDummyTestFrames(FileWrapper* file, uint32_t num_frames) {
void VerifyDummyTestFrames(rtc::File* file, uint32_t num_frames) {
const int kMaxFrameSize = 4;
for (uint32_t i = 1; i <= num_frames; ++i) {
uint8_t frame_header[kFrameHeaderSize];
ASSERT_EQ(kFrameHeaderSize, file->Read(frame_header, kFrameHeaderSize));
ASSERT_EQ(static_cast<unsigned int>(kFrameHeaderSize),
file->Read(frame_header, kFrameHeaderSize));
uint32_t frame_length =
ByteReader<uint32_t>::ReadLittleEndian(&frame_header[0]);
EXPECT_EQ(i % 4, frame_length);
@ -109,67 +105,27 @@ class IvfFileWriterTest : public ::testing::Test {
void RunBasicFileStructureTest(VideoCodecType codec_type,
const uint8_t fourcc[4],
bool use_capture_tims_ms) {
file_writer_ = IvfFileWriter::Open(file_name_, codec_type);
file_writer_ = IvfFileWriter::Wrap(rtc::File::Open(file_name_), 0);
ASSERT_TRUE(file_writer_.get());
const int kWidth = 320;
const int kHeight = 240;
const int kNumFrames = 257;
EXPECT_TRUE(
WriteDummyTestFrames(kWidth, kHeight, kNumFrames, use_capture_tims_ms));
ASSERT_TRUE(WriteDummyTestFrames(codec_type, kWidth, kHeight, kNumFrames,
use_capture_tims_ms));
EXPECT_TRUE(file_writer_->Close());
std::unique_ptr<FileWrapper> out_file(FileWrapper::Create());
ASSERT_TRUE(out_file->OpenFile(file_name_.c_str(), true));
VerifyIvfHeader(out_file.get(), fourcc, kWidth, kHeight, kNumFrames,
rtc::File out_file = rtc::File::Open(file_name_);
VerifyIvfHeader(&out_file, fourcc, kWidth, kHeight, kNumFrames,
use_capture_tims_ms);
VerifyDummyTestFrames(out_file.get(), kNumFrames);
VerifyDummyTestFrames(&out_file, kNumFrames);
out_file->CloseFile();
bool file_removed = false;
for (int i = 0; i < kMaxFileRetries; ++i) {
file_removed = remove(file_name_.c_str()) == 0;
if (file_removed)
break;
// Couldn't remove file for some reason, wait a sec and try again.
rtc::Thread::SleepMs(1000);
}
EXPECT_TRUE(file_removed);
}
// Check whether file exists or not, and if it does not meet expectation,
// wait a bit and check again, up to kMaxFileRetries times. This is an ugly
// hack to avoid flakiness on certain operating systems where antivirus
// software may unexpectedly lock files and keep them from disappearing or
// being reused.
bool FileExists(bool expected) {
bool file_exists = expected;
std::unique_ptr<FileWrapper> file_wrapper;
int iterations = 0;
do {
if (file_wrapper.get() != nullptr)
rtc::Thread::SleepMs(1000);
file_wrapper.reset(FileWrapper::Create());
file_exists = file_wrapper->OpenFile(file_name_.c_str(), true);
file_wrapper->CloseFile();
} while (file_exists != expected && ++iterations < kMaxFileRetries);
return file_exists;
out_file.Close();
}
std::string file_name_;
std::unique_ptr<IvfFileWriter> file_writer_;
};
TEST_F(IvfFileWriterTest, RemovesUnusedFile) {
file_writer_ = IvfFileWriter::Open(file_name_, kVideoCodecVP8);
ASSERT_TRUE(file_writer_.get() != nullptr);
EXPECT_TRUE(FileExists(true));
EXPECT_TRUE(file_writer_->Close());
EXPECT_FALSE(FileExists(false));
EXPECT_FALSE(file_writer_->Close()); // Can't close twice.
}
TEST_F(IvfFileWriterTest, WritesBasicVP8FileNtpTimestamp) {
const uint8_t fourcc[4] = {'V', 'P', '8', '0'};
RunBasicFileStructureTest(kVideoCodecVP8, fourcc, false);
@ -200,4 +156,28 @@ TEST_F(IvfFileWriterTest, WritesBasicH264FileMsTimestamp) {
RunBasicFileStructureTest(kVideoCodecH264, fourcc, true);
}
TEST_F(IvfFileWriterTest, ClosesWhenReachesLimit) {
const uint8_t fourcc[4] = {'V', 'P', '8', '0'};
const int kWidth = 320;
const int kHeight = 240;
const int kNumFramesToWrite = 2;
const int kNumFramesToFit = 1;
file_writer_ = IvfFileWriter::Wrap(
rtc::File::Open(file_name_),
kHeaderSize +
kNumFramesToFit * (kFrameHeaderSize + sizeof(dummy_payload)));
ASSERT_TRUE(file_writer_.get());
ASSERT_FALSE(WriteDummyTestFrames(kVideoCodecVP8, kWidth, kHeight,
kNumFramesToWrite, true));
ASSERT_FALSE(file_writer_->Close());
rtc::File out_file = rtc::File::Open(file_name_);
VerifyIvfHeader(&out_file, fourcc, kWidth, kHeight, kNumFramesToFit, true);
VerifyDummyTestFrames(&out_file, kNumFramesToFit);
out_file.Close();
}
} // namespace webrtc

View File

@ -10,6 +10,8 @@
#include "webrtc/test/fake_encoder.h"
#include <algorithm>
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/base/checks.h"
@ -193,7 +195,10 @@ EncodedImageCallback::Result FakeH264Encoder::OnEncodedImage(
++fragment_counter;
}
}
return callback_->OnEncodedImage(encoded_image, NULL, &fragmentation);
CodecSpecificInfo specifics;
memset(&specifics, 0, sizeof(specifics));
specifics.codecType = kVideoCodecH264;
return callback_->OnEncodedImage(encoded_image, &specifics, &fragmentation);
}
DelayedEncoder::DelayedEncoder(Clock* clock, int delay_ms)

View File

@ -19,6 +19,7 @@
#include "webrtc/base/checks.h"
#include "webrtc/base/event.h"
#include "webrtc/base/file.h"
#include "webrtc/base/optional.h"
#include "webrtc/base/rate_limiter.h"
#include "webrtc/call.h"
@ -3750,4 +3751,105 @@ TEST_F(EndToEndTest, TransportSeqNumOnAudioAndVideo) {
RunBaseTest(&test);
}
class EndToEndLogTest : public EndToEndTest {
void SetUp() { paths_.clear(); }
void TearDown() {
for (const auto& path : paths_) {
rtc::RemoveFile(path);
}
}
public:
int AddFile() {
paths_.push_back(test::TempFilename(test::OutputPath(), "test_file"));
return static_cast<int>(paths_.size()) - 1;
}
rtc::PlatformFile OpenFile(int idx) {
return rtc::OpenPlatformFile(paths_[idx]);
}
void LogSend(bool open) {
if (open) {
video_send_stream_->EnableEncodedFrameRecording(
std::vector<rtc::PlatformFile>(1, OpenFile(AddFile())), 0);
} else {
video_send_stream_->DisableEncodedFrameRecording();
}
}
void LogReceive(bool open) {
if (open) {
video_receive_streams_[0]->EnableEncodedFrameRecording(
OpenFile(AddFile()), 0);
} else {
video_receive_streams_[0]->DisableEncodedFrameRecording();
}
}
std::vector<std::string> paths_;
};
TEST_F(EndToEndLogTest, LogsEncodedFramesWhenRequested) {
static const int kNumFramesToRecord = 10;
class LogEncodingObserver : public test::EndToEndTest,
public EncodedFrameObserver {
public:
explicit LogEncodingObserver(EndToEndLogTest* fixture)
: EndToEndTest(kDefaultTimeoutMs),
fixture_(fixture),
recorded_frames_(0) {}
void PerformTest() override {
fixture_->LogSend(true);
fixture_->LogReceive(true);
ASSERT_TRUE(Wait()) << "Timed out while waiting for frame logging.";
}
void ModifyVideoConfigs(
VideoSendStream::Config* send_config,
std::vector<VideoReceiveStream::Config>* receive_configs,
VideoEncoderConfig* encoder_config) override {
encoder_.reset(VideoEncoder::Create(VideoEncoder::kVp8));
decoder_.reset(VP8Decoder::Create());
send_config->post_encode_callback = this;
send_config->encoder_settings.payload_name = "VP8";
send_config->encoder_settings.encoder = encoder_.get();
(*receive_configs)[0].decoders.resize(1);
(*receive_configs)[0].decoders[0].payload_type =
send_config->encoder_settings.payload_type;
(*receive_configs)[0].decoders[0].payload_name =
send_config->encoder_settings.payload_name;
(*receive_configs)[0].decoders[0].decoder = decoder_.get();
}
void EncodedFrameCallback(const EncodedFrame& encoded_frame) override {
rtc::CritScope lock(&crit_);
if (recorded_frames_++ > kNumFramesToRecord) {
fixture_->LogSend(false);
fixture_->LogReceive(false);
rtc::File send_file(fixture_->OpenFile(0));
rtc::File receive_file(fixture_->OpenFile(1));
uint8_t out[100];
// If logging has worked correctly neither file should be empty, i.e.
// we should be able to read something from them.
EXPECT_LT(0u, send_file.Read(out, 100));
EXPECT_LT(0u, receive_file.Read(out, 100));
observation_complete_.Set();
}
}
private:
EndToEndLogTest* const fixture_;
std::unique_ptr<VideoEncoder> encoder_;
std::unique_ptr<VideoDecoder> decoder_;
rtc::CriticalSection crit_;
int recorded_frames_ GUARDED_BY(crit_);
} test(this);
RunBaseTest(&test);
}
} // namespace webrtc

View File

@ -172,6 +172,14 @@ std::string SL1() {
return static_cast<std::string>(FLAGS_sl1);
}
DEFINE_string(encoded_frame_path,
"",
"The base path for encoded frame logs. Created files will have "
"the form <encoded_frame_path>.<n>.(recv|send.<m>).ivf");
std::string EncodedFramePath() {
return static_cast<std::string>(FLAGS_encoded_frame_path);
}
DEFINE_bool(logs, false, "print logs to stderr");
DEFINE_bool(send_side_bwe, true, "Use send-side bandwidth estimation");
@ -224,12 +232,21 @@ void Loopback() {
call_bitrate_config.max_bitrate_bps = flags::MaxBitrateKbps() * 1000;
VideoQualityTest::Params params;
params.common = {flags::Width(), flags::Height(), flags::Fps(),
flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000,
flags::MaxBitrateKbps() * 1000, false, flags::Codec(),
flags::NumTemporalLayers(), flags::SelectedTL(),
flags::MinTransmitBitrateKbps() * 1000, flags::FLAGS_send_side_bwe,
false, call_bitrate_config};
params.common = {flags::Width(),
flags::Height(),
flags::Fps(),
flags::MinBitrateKbps() * 1000,
flags::TargetBitrateKbps() * 1000,
flags::MaxBitrateKbps() * 1000,
false,
flags::Codec(),
flags::NumTemporalLayers(),
flags::SelectedTL(),
flags::MinTransmitBitrateKbps() * 1000,
flags::FLAGS_send_side_bwe,
false,
flags::EncodedFramePath(),
call_bitrate_config};
params.screenshare = {true, flags::SlideChangeInterval(),
flags::ScrollDuration()};
params.analyzer = {"screenshare", 0.0, 0.0, flags::DurationSecs(),

View File

@ -183,6 +183,14 @@ std::string SL1() {
return static_cast<std::string>(FLAGS_sl1);
}
DEFINE_string(encoded_frame_path,
"",
"The base path for encoded frame logs. Created files will have "
"the form <encoded_frame_path>.<n>.(recv|send.<m>).ivf");
std::string EncodedFramePath() {
return static_cast<std::string>(FLAGS_encoded_frame_path);
}
DEFINE_bool(logs, false, "print logs to stderr");
DEFINE_bool(send_side_bwe, true, "Use send-side bandwidth estimation");
@ -230,12 +238,21 @@ void Loopback() {
call_bitrate_config.max_bitrate_bps = flags::MaxBitrateKbps() * 1000;
VideoQualityTest::Params params;
params.common = {flags::Width(), flags::Height(), flags::Fps(),
flags::MinBitrateKbps() * 1000, flags::TargetBitrateKbps() * 1000,
flags::MaxBitrateKbps() * 1000, flags::FLAGS_suspend_below_min_bitrate,
flags::Codec(), flags::NumTemporalLayers(), flags::SelectedTL(),
0, // No min transmit bitrate.
flags::FLAGS_send_side_bwe, flags::FLAGS_use_fec, call_bitrate_config};
params.common = {flags::Width(),
flags::Height(),
flags::Fps(),
flags::MinBitrateKbps() * 1000,
flags::TargetBitrateKbps() * 1000,
flags::MaxBitrateKbps() * 1000,
flags::FLAGS_suspend_below_min_bitrate,
flags::Codec(),
flags::NumTemporalLayers(),
flags::SelectedTL(),
0, // No min transmit bitrate.
flags::FLAGS_send_side_bwe,
flags::FLAGS_use_fec,
flags::EncodedFramePath(),
call_bitrate_config};
params.video = {flags::Clip()};
params.analyzer = {"video", 0.0, 0.0, flags::DurationSecs(),
flags::OutputFilename(), flags::GraphTitle()};

View File

@ -22,6 +22,7 @@
#include "webrtc/base/event.h"
#include "webrtc/base/format_macros.h"
#include "webrtc/base/optional.h"
#include "webrtc/base/platform_file.h"
#include "webrtc/base/timeutils.h"
#include "webrtc/call.h"
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
@ -798,7 +799,8 @@ class VideoAnalyzer : public PacketReceiver,
rtc::Event done_;
};
VideoQualityTest::VideoQualityTest() : clock_(Clock::GetRealTimeClock()) {}
VideoQualityTest::VideoQualityTest()
: clock_(Clock::GetRealTimeClock()), receive_logs_(0), send_logs_(0) {}
void VideoQualityTest::TestBody() {}
@ -1188,6 +1190,8 @@ void VideoQualityTest::RunWithAnalyzer(const Params& params) {
rtc::VideoSinkWants wants;
capturer_->AddOrUpdateSink(analyzer.InputInterface(), wants);
StartEncodedFrameLogs(video_send_stream_);
StartEncodedFrameLogs(video_receive_streams_[0]);
video_send_stream_->Start();
for (VideoReceiveStream* receive_stream : video_receive_streams_)
receive_stream->Start();
@ -1311,12 +1315,15 @@ void VideoQualityTest::RunWithRenderers(const Params& params) {
if (params_.audio_video_sync)
audio_config.sync_group = kSyncGroup;
audio_receive_stream =call->CreateAudioReceiveStream(audio_config);
audio_receive_stream = call->CreateAudioReceiveStream(audio_config);
const CodecInst kOpusInst = {120, "OPUS", 48000, 960, 2, 64000};
EXPECT_EQ(0, voe.codec->SetSendCodec(voe.send_channel_id, kOpusInst));
}
StartEncodedFrameLogs(video_receive_stream);
StartEncodedFrameLogs(video_send_stream_);
// Start sending and receiving video.
video_receive_stream->Start();
video_send_stream_->Start();
@ -1364,4 +1371,29 @@ void VideoQualityTest::RunWithRenderers(const Params& params) {
DestroyVoiceEngine(&voe);
}
void VideoQualityTest::StartEncodedFrameLogs(VideoSendStream* stream) {
if (!params_.common.encoded_frame_base_path.empty()) {
std::ostringstream str;
str << send_logs_++;
std::string prefix =
params_.common.encoded_frame_base_path + "." + str.str() + ".send.";
stream->EnableEncodedFrameRecording(
std::vector<rtc::PlatformFile>(
{rtc::CreatePlatformFile(prefix + "1.ivf"),
rtc::CreatePlatformFile(prefix + "2.ivf"),
rtc::CreatePlatformFile(prefix + "3.ivf")}),
10000000);
}
}
void VideoQualityTest::StartEncodedFrameLogs(VideoReceiveStream* stream) {
if (!params_.common.encoded_frame_base_path.empty()) {
std::ostringstream str;
str << receive_logs_++;
std::string path =
params_.common.encoded_frame_base_path + "." + str.str() + ".recv.ivf";
stream->EnableEncodedFrameRecording(rtc::CreatePlatformFile(path),
10000000);
}
}
} // namespace webrtc

View File

@ -41,6 +41,7 @@ class VideoQualityTest : public test::CallTest {
int min_transmit_bps;
bool send_side_bwe;
bool fec;
std::string encoded_frame_base_path;
Call::Config::BitrateConfig call_bitrate_config;
} common;
@ -107,6 +108,9 @@ class VideoQualityTest : public test::CallTest {
void SetupCommon(Transport* send_transport, Transport* recv_transport);
void SetupScreenshare();
void StartEncodedFrameLogs(VideoSendStream* stream);
void StartEncodedFrameLogs(VideoReceiveStream* stream);
// We need a more general capturer than the FrameGeneratorCapturer.
std::unique_ptr<test::VideoCapturer> capturer_;
std::unique_ptr<test::TraceToStderr> trace_to_stderr_;
@ -114,6 +118,9 @@ class VideoQualityTest : public test::CallTest {
std::unique_ptr<VideoEncoder> encoder_;
Clock* const clock_;
int receive_logs_;
int send_logs_;
Params params_;
};

View File

@ -31,8 +31,6 @@
namespace webrtc {
static const bool kEnableFrameRecording = false;
static bool UseSendSideBwe(const VideoReceiveStream::Config& config) {
if (!config.rtp.transport_cc)
return false;
@ -365,16 +363,12 @@ EncodedImageCallback::Result VideoReceiveStream::OnEncodedImage(
EncodedFrame(encoded_image._buffer, encoded_image._length,
encoded_image._frameType));
}
if (kEnableFrameRecording) {
if (!ivf_writer_.get()) {
RTC_DCHECK(codec_specific_info);
std::ostringstream oss;
oss << "receive_bitstream_ssrc_" << config_.rtp.remote_ssrc << ".ivf";
ivf_writer_ =
IvfFileWriter::Open(oss.str(), codec_specific_info->codecType);
}
{
rtc::CritScope lock(&ivf_writer_lock_);
if (ivf_writer_.get()) {
bool ok = ivf_writer_->WriteFrame(encoded_image);
RTC_DCHECK(codec_specific_info);
bool ok = ivf_writer_->WriteFrame(encoded_image,
codec_specific_info->codecType);
RTC_DCHECK(ok);
}
}
@ -397,6 +391,24 @@ void VideoReceiveStream::SendNack(
rtp_stream_receiver_.RequestPacketRetransmit(sequence_numbers);
}
void VideoReceiveStream::EnableEncodedFrameRecording(rtc::PlatformFile file,
size_t byte_limit) {
{
rtc::CritScope lock(&ivf_writer_lock_);
if (file == rtc::kInvalidPlatformFileValue) {
ivf_writer_.reset();
} else {
ivf_writer_ = IvfFileWriter::Wrap(rtc::File(file), byte_limit);
}
}
if (file != rtc::kInvalidPlatformFileValue) {
// Make a keyframe appear as early as possible in the logs, to give actually
// decodable output.
RequestKeyFrame();
}
}
void VideoReceiveStream::RequestKeyFrame() {
rtp_stream_receiver_.RequestKeyFrame();
}

View File

@ -84,6 +84,14 @@ class VideoReceiveStream : public webrtc::VideoReceiveStream,
// Implements KeyFrameRequestSender.
void RequestKeyFrame() override;
// Takes ownership of the file, is responsible for closing it later.
// Calling this method will close and finalize any current log.
// Giving rtc::kInvalidPlatformFileValue disables logging.
// If a frame to be written would make the log too large the write fails and
// the log is closed and finalized. A |byte_limit| of 0 means no limit.
void EnableEncodedFrameRecording(rtc::PlatformFile file,
size_t byte_limit) override;
private:
static bool DecodeThreadFunction(void* ptr);
void Decode();
@ -105,7 +113,8 @@ class VideoReceiveStream : public webrtc::VideoReceiveStream,
std::unique_ptr<VideoStreamDecoder> video_stream_decoder_;
RtpStreamsSynchronizer rtp_stream_sync_;
std::unique_ptr<IvfFileWriter> ivf_writer_;
rtc::CriticalSection ivf_writer_lock_;
std::unique_ptr<IvfFileWriter> ivf_writer_ GUARDED_BY(ivf_writer_lock_);
};
} // namespace internal
} // namespace webrtc

View File

@ -15,7 +15,9 @@
#include <utility>
#include <vector>
#include "webrtc/common_types.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/file.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/trace_event.h"
#include "webrtc/modules/bitrate_controller/include/bitrate_controller.h"
@ -267,6 +269,9 @@ class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver,
VideoSendStream::RtpStateMap GetRtpStates() const;
void EnableEncodedFrameRecording(const std::vector<rtc::PlatformFile>& files,
size_t byte_limit);
private:
class CheckEncoderActivityTask;
class EncoderReconfiguredTask;
@ -316,9 +321,9 @@ class VideoSendStreamImpl : public webrtc::BitrateAllocatorObserver,
BitrateAllocator* const bitrate_allocator_;
VieRemb* const remb_;
static const bool kEnableFrameRecording = false;
static const int kMaxLayers = 3;
std::unique_ptr<IvfFileWriter> file_writers_[kMaxLayers];
rtc::CriticalSection ivf_writers_crit_;
std::unique_ptr<IvfFileWriter> file_writers_[kMaxSimulcastStreams] GUARDED_BY(
ivf_writers_crit_);
int max_padding_bitrate_;
int encoder_min_bitrate_bps_;
@ -615,6 +620,12 @@ bool VideoSendStream::DeliverRtcp(const uint8_t* packet, size_t length) {
return send_stream_->DeliverRtcp(packet, length);
}
void VideoSendStream::EnableEncodedFrameRecording(
const std::vector<rtc::PlatformFile>& files,
size_t byte_limit) {
send_stream_->EnableEncodedFrameRecording(files, byte_limit);
}
VideoSendStreamImpl::VideoSendStreamImpl(
SendStatisticsProxy* stats_proxy,
rtc::TaskQueue* worker_queue,
@ -891,25 +902,16 @@ EncodedImageCallback::Result VideoSendStreamImpl::OnEncodedImage(
EncodedImageCallback::Result result = payload_router_.OnEncodedImage(
encoded_image, codec_specific_info, fragmentation);
if (kEnableFrameRecording) {
int layer = codec_specific_info->codecType == kVideoCodecVP8
? codec_specific_info->codecSpecific.VP8.simulcastIdx
: 0;
IvfFileWriter* file_writer;
{
if (file_writers_[layer] == nullptr) {
std::ostringstream oss;
oss << "send_bitstream_ssrc";
for (uint32_t ssrc : config_->rtp.ssrcs)
oss << "_" << ssrc;
oss << "_layer" << layer << ".ivf";
file_writers_[layer] =
IvfFileWriter::Open(oss.str(), codec_specific_info->codecType);
}
file_writer = file_writers_[layer].get();
}
if (file_writer) {
bool ok = file_writer->WriteFrame(encoded_image);
RTC_DCHECK(codec_specific_info);
int layer = codec_specific_info->codecType == kVideoCodecVP8
? codec_specific_info->codecSpecific.VP8.simulcastIdx
: 0;
{
rtc::CritScope lock(&ivf_writers_crit_);
if (file_writers_[layer].get()) {
bool ok = file_writers_[layer]->WriteFrame(
encoded_image, codec_specific_info->codecType);
RTC_DCHECK(ok);
}
}
@ -1064,6 +1066,27 @@ uint32_t VideoSendStreamImpl::OnBitrateUpdated(uint32_t bitrate_bps,
return protection_bitrate;
}
void VideoSendStreamImpl::EnableEncodedFrameRecording(
const std::vector<rtc::PlatformFile>& files,
size_t byte_limit) {
{
rtc::CritScope lock(&ivf_writers_crit_);
for (unsigned int i = 0; i < kMaxSimulcastStreams; ++i) {
if (i < files.size()) {
file_writers_[i] = IvfFileWriter::Wrap(rtc::File(files[i]), byte_limit);
} else {
file_writers_[i].reset();
}
}
}
if (!files.empty()) {
// Make a keyframe appear as early as possible in the logs, to give actually
// decodable output.
vie_encoder_->SendKeyFrame();
}
}
int VideoSendStreamImpl::ProtectionRequest(
const FecProtectionParams* delta_params,
const FecProtectionParams* key_params,

View File

@ -79,6 +79,16 @@ class VideoSendStream : public webrtc::VideoSendStream {
Stats GetStats() override;
typedef std::map<uint32_t, RtpState> RtpStateMap;
// Takes ownership of each file, is responsible for closing them later.
// Calling this method will close and finalize any current logs.
// Giving rtc::kInvalidPlatformFileValue in any position disables logging
// for the corresponding stream.
// If a frame to be written would make the log too large the write fails and
// the log is closed and finalized. A |byte_limit| of 0 means no limit.
void EnableEncodedFrameRecording(const std::vector<rtc::PlatformFile>& files,
size_t byte_limit) override;
RtpStateMap StopPermanentlyAndGetRtpStates();
private:

View File

@ -16,6 +16,7 @@
#include <string>
#include <vector>
#include "webrtc/base/platform_file.h"
#include "webrtc/common_types.h"
#include "webrtc/common_video/include/frame_callback.h"
#include "webrtc/config.h"
@ -208,6 +209,17 @@ class VideoReceiveStream {
// TODO(pbos): Add info on currently-received codec to Stats.
virtual Stats GetStats() const = 0;
// Takes ownership of the file, is responsible for closing it later.
// Calling this method will close and finalize any current log.
// Giving rtc::kInvalidPlatformFileValue disables logging.
// If a frame to be written would make the log too large the write fails and
// the log is closed and finalized. A |byte_limit| of 0 means no limit.
virtual void EnableEncodedFrameRecording(rtc::PlatformFile file,
size_t byte_limit) = 0;
inline void DisableEncodedFrameRecording() {
EnableEncodedFrameRecording(rtc::kInvalidPlatformFileValue, 0);
}
protected:
virtual ~VideoReceiveStream() {}
};

View File

@ -13,8 +13,10 @@
#include <map>
#include <string>
#include <utility>
#include <vector>
#include "webrtc/base/platform_file.h"
#include "webrtc/common_types.h"
#include "webrtc/common_video/include/frame_callback.h"
#include "webrtc/config.h"
@ -192,6 +194,22 @@ class VideoSendStream {
virtual Stats GetStats() = 0;
// Takes ownership of each file, is responsible for closing them later.
// Calling this method will close and finalize any current logs.
// Some codecs produce multiple streams (VP8 only at present), each of these
// streams will log to a separate file. kMaxSimulcastStreams in common_types.h
// gives the max number of such streams. If there is no file for a stream, or
// the file is rtc::kInvalidPlatformFileValue, frames from that stream will
// not be logged.
// If a frame to be written would make the log too large the write fails and
// the log is closed and finalized. A |byte_limit| of 0 means no limit.
virtual void EnableEncodedFrameRecording(
const std::vector<rtc::PlatformFile>& files,
size_t byte_limit) = 0;
inline void DisableEncodedFrameRecording() {
EnableEncodedFrameRecording(std::vector<rtc::PlatformFile>(), 0);
}
protected:
virtual ~VideoSendStream() {}
};