Expose Ivf logging through the native API
BUG=webrtc:6300
Review-Url: https://codereview.webrtc.org/2303273002
Cr-Commit-Position: refs/heads/master@{#14419}
diff --git a/webrtc/media/engine/fakewebrtccall.cc b/webrtc/media/engine/fakewebrtccall.cc
index 9670f13..9515505 100644
--- a/webrtc/media/engine/fakewebrtccall.cc
+++ b/webrtc/media/engine/fakewebrtccall.cc
@@ -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 @@
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 @@
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),
diff --git a/webrtc/media/engine/fakewebrtccall.h b/webrtc/media/engine/fakewebrtccall.h
index 29142c1..170e6fc 100644
--- a/webrtc/media/engine/fakewebrtccall.h
+++ b/webrtc/media/engine/fakewebrtccall.h
@@ -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 @@
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 @@
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 @@
};
} // namespace cricket
-#endif // TALK_MEDIA_WEBRTC_WEBRTCVIDEOENGINE2_UNITTEST_H_
+#endif // WEBRTC_MEDIA_ENGINE_FAKEWEBRTCCALL_H_
diff --git a/webrtc/modules/video_coding/utility/ivf_file_writer.cc b/webrtc/modules/video_coding/utility/ivf_file_writer.cc
index a80cf9b..4198dfe 100644
--- a/webrtc/modules/video_coding/utility/ivf_file_writer.cc
+++ b/webrtc/modules/video_coding/utility/ivf_file_writer.cc
@@ -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 @@
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 @@
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 @@
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;
}
diff --git a/webrtc/modules/video_coding/utility/ivf_file_writer.h b/webrtc/modules/video_coding/utility/ivf_file_writer.h
index 25d68a2..b556111 100644
--- a/webrtc/modules/video_coding/utility/ivf_file_writer.h
+++ b/webrtc/modules/video_coding/utility/ivf_file_writer.h
@@ -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);
};
diff --git a/webrtc/modules/video_coding/utility/ivf_file_writer_unittest.cc b/webrtc/modules/video_coding/utility/ivf_file_writer_unittest.cc
index f22aff7..e5da48c 100644
--- a/webrtc/modules/video_coding/utility/ivf_file_writer_unittest.cc
+++ b/webrtc/modules/video_coding/utility/ivf_file_writer_unittest.cc
@@ -26,23 +26,18 @@
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 @@
} 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 @@
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 @@
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 @@
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
diff --git a/webrtc/test/fake_encoder.cc b/webrtc/test/fake_encoder.cc
index 29f844c..d015ab8 100644
--- a/webrtc/test/fake_encoder.cc
+++ b/webrtc/test/fake_encoder.cc
@@ -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 @@
++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)
diff --git a/webrtc/video/end_to_end_tests.cc b/webrtc/video/end_to_end_tests.cc
index b3d667e..021ec3a 100644
--- a/webrtc/video/end_to_end_tests.cc
+++ b/webrtc/video/end_to_end_tests.cc
@@ -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 @@
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
diff --git a/webrtc/video/screenshare_loopback.cc b/webrtc/video/screenshare_loopback.cc
index 82e51dd..2d1e1d3 100644
--- a/webrtc/video/screenshare_loopback.cc
+++ b/webrtc/video/screenshare_loopback.cc
@@ -172,6 +172,14 @@
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 @@
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(),
diff --git a/webrtc/video/video_loopback.cc b/webrtc/video/video_loopback.cc
index 10998c8..da73640 100644
--- a/webrtc/video/video_loopback.cc
+++ b/webrtc/video/video_loopback.cc
@@ -183,6 +183,14 @@
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 @@
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()};
diff --git a/webrtc/video/video_quality_test.cc b/webrtc/video/video_quality_test.cc
index f9440ad..ea0e0d9 100644
--- a/webrtc/video/video_quality_test.cc
+++ b/webrtc/video/video_quality_test.cc
@@ -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 @@
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 @@
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 @@
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 @@
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
diff --git a/webrtc/video/video_quality_test.h b/webrtc/video/video_quality_test.h
index 805a559..cd4939d 100644
--- a/webrtc/video/video_quality_test.h
+++ b/webrtc/video/video_quality_test.h
@@ -41,6 +41,7 @@
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 @@
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 @@
std::unique_ptr<VideoEncoder> encoder_;
Clock* const clock_;
+ int receive_logs_;
+ int send_logs_;
+
Params params_;
};
diff --git a/webrtc/video/video_receive_stream.cc b/webrtc/video/video_receive_stream.cc
index 01568cb..6e9633f 100644
--- a/webrtc/video/video_receive_stream.cc
+++ b/webrtc/video/video_receive_stream.cc
@@ -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 @@
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 @@
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();
}
diff --git a/webrtc/video/video_receive_stream.h b/webrtc/video/video_receive_stream.h
index 3cffb4b..416cfe9 100644
--- a/webrtc/video/video_receive_stream.h
+++ b/webrtc/video/video_receive_stream.h
@@ -84,6 +84,14 @@
// 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 @@
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
diff --git a/webrtc/video/video_send_stream.cc b/webrtc/video/video_send_stream.cc
index c8541fe..c998272 100644
--- a/webrtc/video/video_send_stream.cc
+++ b/webrtc/video/video_send_stream.cc
@@ -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 @@
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 @@
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 @@
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 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 @@
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,
diff --git a/webrtc/video/video_send_stream.h b/webrtc/video/video_send_stream.h
index dd5081d..58433ad 100644
--- a/webrtc/video/video_send_stream.h
+++ b/webrtc/video/video_send_stream.h
@@ -79,6 +79,16 @@
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:
diff --git a/webrtc/video_receive_stream.h b/webrtc/video_receive_stream.h
index 0adcf3f..def103f 100644
--- a/webrtc/video_receive_stream.h
+++ b/webrtc/video_receive_stream.h
@@ -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 @@
// 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() {}
};
diff --git a/webrtc/video_send_stream.h b/webrtc/video_send_stream.h
index 2948d46..e987784 100644
--- a/webrtc/video_send_stream.h
+++ b/webrtc/video_send_stream.h
@@ -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 @@
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() {}
};