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() {}
 };