Added option to specify a maximum file size when recording an AEC dump.

For applications with a strict filesize limit for debug files,
I added an option to specify a maximum filesize for AEC dumps. An
existing unit test is extended to check that the feature works as
advertised.

BUG=webrtc:4741
TBR=glaznev@webrtc.org

Review URL: https://codereview.webrtc.org/1413483003

Cr-Commit-Position: refs/heads/master@{#11081}
diff --git a/webrtc/modules/audio_processing/audio_processing_impl.cc b/webrtc/modules/audio_processing/audio_processing_impl.cc
index a332945..b79b4f0 100644
--- a/webrtc/modules/audio_processing/audio_processing_impl.cc
+++ b/webrtc/modules/audio_processing/audio_processing_impl.cc
@@ -632,6 +632,7 @@
     for (int i = 0; i < formats_.api_format.output_stream().num_channels(); ++i)
       msg->add_output_channel(dest[i], channel_size);
     RETURN_ON_ERR(WriteMessageToDebugFile(debug_dump_.debug_file.get(),
+                                          &debug_dump_.num_bytes_left_for_log_,
                                           &crit_debug_, &debug_dump_.capture));
   }
 #endif
@@ -719,6 +720,7 @@
         sizeof(int16_t) * frame->samples_per_channel_ * frame->num_channels_;
     msg->set_output_data(frame->data_, data_size);
     RETURN_ON_ERR(WriteMessageToDebugFile(debug_dump_.debug_file.get(),
+                                          &debug_dump_.num_bytes_left_for_log_,
                                           &crit_debug_, &debug_dump_.capture));
   }
 #endif
@@ -886,6 +888,7 @@
          i < formats_.api_format.reverse_input_stream().num_channels(); ++i)
       msg->add_channel(src[i], channel_size);
     RETURN_ON_ERR(WriteMessageToDebugFile(debug_dump_.debug_file.get(),
+                                          &debug_dump_.num_bytes_left_for_log_,
                                           &crit_debug_, &debug_dump_.render));
   }
 #endif
@@ -954,6 +957,7 @@
         sizeof(int16_t) * frame->samples_per_channel_ * frame->num_channels_;
     msg->set_data(frame->data_, data_size);
     RETURN_ON_ERR(WriteMessageToDebugFile(debug_dump_.debug_file.get(),
+                                          &debug_dump_.num_bytes_left_for_log_,
                                           &crit_debug_, &debug_dump_.render));
   }
 #endif
@@ -1039,7 +1043,8 @@
 }
 
 int AudioProcessingImpl::StartDebugRecording(
-    const char filename[AudioProcessing::kMaxFilenameSize]) {
+    const char filename[AudioProcessing::kMaxFilenameSize],
+    int64_t max_log_size_bytes) {
   // Run in a single-threaded manner.
   rtc::CritScope cs_render(&crit_render_);
   rtc::CritScope cs_capture(&crit_capture_);
@@ -1050,6 +1055,7 @@
   }
 
 #ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
+  debug_dump_.num_bytes_left_for_log_ = max_log_size_bytes;
   // Stop any ongoing recording.
   if (debug_dump_.debug_file->Open()) {
     if (debug_dump_.debug_file->CloseFile() == -1) {
@@ -1070,7 +1076,8 @@
 #endif  // WEBRTC_AUDIOPROC_DEBUG_DUMP
 }
 
-int AudioProcessingImpl::StartDebugRecording(FILE* handle) {
+int AudioProcessingImpl::StartDebugRecording(FILE* handle,
+                                             int64_t max_log_size_bytes) {
   // Run in a single-threaded manner.
   rtc::CritScope cs_render(&crit_render_);
   rtc::CritScope cs_capture(&crit_capture_);
@@ -1080,6 +1087,8 @@
   }
 
 #ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
+  debug_dump_.num_bytes_left_for_log_ = max_log_size_bytes;
+
   // Stop any ongoing recording.
   if (debug_dump_.debug_file->Open()) {
     if (debug_dump_.debug_file->CloseFile() == -1) {
@@ -1105,7 +1114,7 @@
   rtc::CritScope cs_render(&crit_render_);
   rtc::CritScope cs_capture(&crit_capture_);
   FILE* stream = rtc::FdopenPlatformFileForWriting(handle);
-  return StartDebugRecording(stream);
+  return StartDebugRecording(stream, -1);
 }
 
 int AudioProcessingImpl::StopDebugRecording() {
@@ -1400,6 +1409,7 @@
 #ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
 int AudioProcessingImpl::WriteMessageToDebugFile(
     FileWrapper* debug_file,
+    int64_t* filesize_limit_bytes,
     rtc::CriticalSection* crit_debug,
     ApmDebugDumpThreadState* debug_state) {
   int32_t size = debug_state->event_msg->ByteSize();
@@ -1417,7 +1427,19 @@
 
   {
     // Ensure atomic writes of the message.
-    rtc::CritScope cs_capture(crit_debug);
+    rtc::CritScope cs_debug(crit_debug);
+
+    RTC_DCHECK(debug_file->Open());
+    // Update the byte counter.
+    if (*filesize_limit_bytes >= 0) {
+      *filesize_limit_bytes -=
+          (sizeof(int32_t) + debug_state->event_str.length());
+      if (*filesize_limit_bytes < 0) {
+        // Not enough bytes are left to write this message, so stop logging.
+        debug_file->CloseFile();
+        return kNoError;
+      }
+    }
     // Write message preceded by its size.
     if (!debug_file->Write(&size, sizeof(int32_t))) {
       return kFileError;
@@ -1452,6 +1474,7 @@
   // debug_dump_.capture.event_msg.
 
   RETURN_ON_ERR(WriteMessageToDebugFile(debug_dump_.debug_file.get(),
+                                        &debug_dump_.num_bytes_left_for_log_,
                                         &crit_debug_, &debug_dump_.capture));
   return kNoError;
 }
@@ -1504,6 +1527,7 @@
   debug_dump_.capture.event_msg->mutable_config()->CopyFrom(config);
 
   RETURN_ON_ERR(WriteMessageToDebugFile(debug_dump_.debug_file.get(),
+                                        &debug_dump_.num_bytes_left_for_log_,
                                         &crit_debug_, &debug_dump_.capture));
   return kNoError;
 }
diff --git a/webrtc/modules/audio_processing/audio_processing_impl.h b/webrtc/modules/audio_processing/audio_processing_impl.h
index 3506ac4..b720bbf 100644
--- a/webrtc/modules/audio_processing/audio_processing_impl.h
+++ b/webrtc/modules/audio_processing/audio_processing_impl.h
@@ -57,8 +57,9 @@
   int Initialize(const ProcessingConfig& processing_config) override;
   void SetExtraOptions(const Config& config) override;
   void UpdateHistogramsOnCallEnd() override;
-  int StartDebugRecording(const char filename[kMaxFilenameSize]) override;
-  int StartDebugRecording(FILE* handle) override;
+  int StartDebugRecording(const char filename[kMaxFilenameSize],
+                          int64_t max_log_size_bytes) override;
+  int StartDebugRecording(FILE* handle, int64_t max_log_size_bytes) override;
   int StartDebugRecordingForPlatformFile(rtc::PlatformFile handle) override;
   int StopDebugRecording() override;
 
@@ -143,6 +144,9 @@
 
   struct ApmDebugDumpState {
     ApmDebugDumpState() : debug_file(FileWrapper::Create()) {}
+    // Number of bytes that can still be written to the log before the maximum
+    // size is reached. A value of <= 0 indicates that no limit is used.
+    int64_t num_bytes_left_for_log_ = -1;
     rtc::scoped_ptr<FileWrapper> debug_file;
     ApmDebugDumpThreadState render;
     ApmDebugDumpThreadState capture;
@@ -221,6 +225,7 @@
   // TODO(andrew): make this more graceful. Ideally we would split this stuff
   // out into a separate class with an "enabled" and "disabled" implementation.
   static int WriteMessageToDebugFile(FileWrapper* debug_file,
+                                     int64_t* filesize_limit_bytes,
                                      rtc::CriticalSection* crit_debug,
                                      ApmDebugDumpThreadState* debug_state);
   int WriteInitMessage() EXCLUSIVE_LOCKS_REQUIRED(crit_render_, crit_capture_);
diff --git a/webrtc/modules/audio_processing/include/audio_processing.h b/webrtc/modules/audio_processing/include/audio_processing.h
index 5fcc4d4..74ceb4c 100644
--- a/webrtc/modules/audio_processing/include/audio_processing.h
+++ b/webrtc/modules/audio_processing/include/audio_processing.h
@@ -408,13 +408,17 @@
   // Starts recording debugging information to a file specified by |filename|,
   // a NULL-terminated string. If there is an ongoing recording, the old file
   // will be closed, and recording will continue in the newly specified file.
-  // An already existing file will be overwritten without warning.
+  // An already existing file will be overwritten without warning. A maximum
+  // file size (in bytes) for the log can be specified. The logging is stopped
+  // once the limit has been reached. If max_log_size_bytes is set to a value
+  // <= 0, no limit will be used.
   static const size_t kMaxFilenameSize = 1024;
-  virtual int StartDebugRecording(const char filename[kMaxFilenameSize]) = 0;
+  virtual int StartDebugRecording(const char filename[kMaxFilenameSize],
+                                  int64_t max_log_size_bytes) = 0;
 
   // Same as above but uses an existing file handle. Takes ownership
   // of |handle| and closes it at StopDebugRecording().
-  virtual int StartDebugRecording(FILE* handle) = 0;
+  virtual int StartDebugRecording(FILE* handle, int64_t max_log_size_bytes) = 0;
 
   // Same as above but uses an existing PlatformFile handle. Takes ownership
   // of |handle| and closes it at StopDebugRecording().
diff --git a/webrtc/modules/audio_processing/include/mock_audio_processing.h b/webrtc/modules/audio_processing/include/mock_audio_processing.h
index 4ff52ba..3aea406 100644
--- a/webrtc/modules/audio_processing/include/mock_audio_processing.h
+++ b/webrtc/modules/audio_processing/include/mock_audio_processing.h
@@ -250,10 +250,11 @@
       void(int offset));
   MOCK_CONST_METHOD0(delay_offset_ms,
       int());
-  MOCK_METHOD1(StartDebugRecording,
-      int(const char filename[kMaxFilenameSize]));
-  MOCK_METHOD1(StartDebugRecording,
-      int(FILE* handle));
+  MOCK_METHOD2(StartDebugRecording,
+               int(const char filename[kMaxFilenameSize],
+                   int64_t max_log_size_bytes));
+  MOCK_METHOD2(StartDebugRecording,
+               int(FILE* handle, int64_t max_log_size_bytes));
   MOCK_METHOD0(StopDebugRecording,
       int());
   MOCK_METHOD0(UpdateHistogramsOnCallEnd, void());
diff --git a/webrtc/modules/audio_processing/test/audio_processing_unittest.cc b/webrtc/modules/audio_processing/test/audio_processing_unittest.cc
index eff791d..b6b94f0 100644
--- a/webrtc/modules/audio_processing/test/audio_processing_unittest.cc
+++ b/webrtc/modules/audio_processing/test/audio_processing_unittest.cc
@@ -388,7 +388,8 @@
   int AnalyzeReverseStreamChooser(Format format);
   void ProcessDebugDump(const std::string& in_filename,
                         const std::string& out_filename,
-                        Format format);
+                        Format format,
+                        int max_size_bytes);
   void VerifyDebugDumpTest(Format format);
 
   const std::string output_path_;
@@ -1711,7 +1712,8 @@
 #ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
 void ApmTest::ProcessDebugDump(const std::string& in_filename,
                                const std::string& out_filename,
-                               Format format) {
+                               Format format,
+                               int max_size_bytes) {
   FILE* in_file = fopen(in_filename.c_str(), "rb");
   ASSERT_TRUE(in_file != NULL);
   audioproc::Event event_msg;
@@ -1739,7 +1741,8 @@
       if (first_init) {
         // StartDebugRecording() writes an additional init message. Don't start
         // recording until after the first init to avoid the extra message.
-        EXPECT_NOERR(apm_->StartDebugRecording(out_filename.c_str()));
+        EXPECT_NOERR(
+            apm_->StartDebugRecording(out_filename.c_str(), max_size_bytes));
         first_init = false;
       }
 
@@ -1812,34 +1815,54 @@
       test::OutputPath(), std::string("ref") + format_string + "_aecdump");
   const std::string out_filename = test::TempFilename(
       test::OutputPath(), std::string("out") + format_string + "_aecdump");
+  const std::string limited_filename = test::TempFilename(
+      test::OutputPath(), std::string("limited") + format_string + "_aecdump");
+  const size_t logging_limit_bytes = 100000;
+  // We expect at least this many bytes in the created logfile.
+  const size_t logging_expected_bytes = 95000;
   EnableAllComponents();
-  ProcessDebugDump(in_filename, ref_filename, format);
-  ProcessDebugDump(ref_filename, out_filename, format);
+  ProcessDebugDump(in_filename, ref_filename, format, -1);
+  ProcessDebugDump(ref_filename, out_filename, format, -1);
+  ProcessDebugDump(ref_filename, limited_filename, format, logging_limit_bytes);
 
   FILE* ref_file = fopen(ref_filename.c_str(), "rb");
   FILE* out_file = fopen(out_filename.c_str(), "rb");
+  FILE* limited_file = fopen(limited_filename.c_str(), "rb");
   ASSERT_TRUE(ref_file != NULL);
   ASSERT_TRUE(out_file != NULL);
+  ASSERT_TRUE(limited_file != NULL);
   rtc::scoped_ptr<uint8_t[]> ref_bytes;
   rtc::scoped_ptr<uint8_t[]> out_bytes;
+  rtc::scoped_ptr<uint8_t[]> limited_bytes;
 
   size_t ref_size = ReadMessageBytesFromFile(ref_file, &ref_bytes);
   size_t out_size = ReadMessageBytesFromFile(out_file, &out_bytes);
+  size_t limited_size = ReadMessageBytesFromFile(limited_file, &limited_bytes);
   size_t bytes_read = 0;
+  size_t bytes_read_limited = 0;
   while (ref_size > 0 && out_size > 0) {
     bytes_read += ref_size;
+    bytes_read_limited += limited_size;
     EXPECT_EQ(ref_size, out_size);
+    EXPECT_GE(ref_size, limited_size);
     EXPECT_EQ(0, memcmp(ref_bytes.get(), out_bytes.get(), ref_size));
+    EXPECT_EQ(0, memcmp(ref_bytes.get(), limited_bytes.get(), limited_size));
     ref_size = ReadMessageBytesFromFile(ref_file, &ref_bytes);
     out_size = ReadMessageBytesFromFile(out_file, &out_bytes);
+    limited_size = ReadMessageBytesFromFile(limited_file, &limited_bytes);
   }
   EXPECT_GT(bytes_read, 0u);
+  EXPECT_GT(bytes_read_limited, logging_expected_bytes);
+  EXPECT_LE(bytes_read_limited, logging_limit_bytes);
   EXPECT_NE(0, feof(ref_file));
   EXPECT_NE(0, feof(out_file));
+  EXPECT_NE(0, feof(limited_file));
   ASSERT_EQ(0, fclose(ref_file));
   ASSERT_EQ(0, fclose(out_file));
+  ASSERT_EQ(0, fclose(limited_file));
   remove(ref_filename.c_str());
   remove(out_filename.c_str());
+  remove(limited_filename.c_str());
 }
 
 TEST_F(ApmTest, VerifyDebugDumpInt) {
@@ -1856,13 +1879,13 @@
   const std::string filename =
       test::TempFilename(test::OutputPath(), "debug_aec");
   EXPECT_EQ(apm_->kNullPointerError,
-            apm_->StartDebugRecording(static_cast<const char*>(NULL)));
+            apm_->StartDebugRecording(static_cast<const char*>(NULL), -1));
 
 #ifdef WEBRTC_AUDIOPROC_DEBUG_DUMP
   // Stopping without having started should be OK.
   EXPECT_EQ(apm_->kNoError, apm_->StopDebugRecording());
 
-  EXPECT_EQ(apm_->kNoError, apm_->StartDebugRecording(filename.c_str()));
+  EXPECT_EQ(apm_->kNoError, apm_->StartDebugRecording(filename.c_str(), -1));
   EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_));
   EXPECT_EQ(apm_->kNoError, apm_->AnalyzeReverseStream(revframe_));
   EXPECT_EQ(apm_->kNoError, apm_->StopDebugRecording());
@@ -1876,7 +1899,7 @@
   ASSERT_EQ(0, remove(filename.c_str()));
 #else
   EXPECT_EQ(apm_->kUnsupportedFunctionError,
-            apm_->StartDebugRecording(filename.c_str()));
+            apm_->StartDebugRecording(filename.c_str(), -1));
   EXPECT_EQ(apm_->kUnsupportedFunctionError, apm_->StopDebugRecording());
 
   // Verify the file has NOT been written.
@@ -1887,7 +1910,7 @@
 // TODO(andrew): expand test to verify output.
 TEST_F(ApmTest, DebugDumpFromFileHandle) {
   FILE* fid = NULL;
-  EXPECT_EQ(apm_->kNullPointerError, apm_->StartDebugRecording(fid));
+  EXPECT_EQ(apm_->kNullPointerError, apm_->StartDebugRecording(fid, -1));
   const std::string filename =
       test::TempFilename(test::OutputPath(), "debug_aec");
   fid = fopen(filename.c_str(), "w");
@@ -1897,7 +1920,7 @@
   // Stopping without having started should be OK.
   EXPECT_EQ(apm_->kNoError, apm_->StopDebugRecording());
 
-  EXPECT_EQ(apm_->kNoError, apm_->StartDebugRecording(fid));
+  EXPECT_EQ(apm_->kNoError, apm_->StartDebugRecording(fid, -1));
   EXPECT_EQ(apm_->kNoError, apm_->AnalyzeReverseStream(revframe_));
   EXPECT_EQ(apm_->kNoError, apm_->ProcessStream(frame_));
   EXPECT_EQ(apm_->kNoError, apm_->StopDebugRecording());
@@ -1911,7 +1934,7 @@
   ASSERT_EQ(0, remove(filename.c_str()));
 #else
   EXPECT_EQ(apm_->kUnsupportedFunctionError,
-            apm_->StartDebugRecording(fid));
+            apm_->StartDebugRecording(fid, -1));
   EXPECT_EQ(apm_->kUnsupportedFunctionError, apm_->StopDebugRecording());
 
   ASSERT_EQ(0, fclose(fid));
diff --git a/webrtc/modules/audio_processing/test/debug_dump_test.cc b/webrtc/modules/audio_processing/test/debug_dump_test.cc
index d2dd9c8..6aa310c 100644
--- a/webrtc/modules/audio_processing/test/debug_dump_test.cc
+++ b/webrtc/modules/audio_processing/test/debug_dump_test.cc
@@ -181,7 +181,7 @@
 }
 
 void DebugDumpGenerator::StartRecording() {
-  apm_->StartDebugRecording(dump_file_name_.c_str());
+  apm_->StartDebugRecording(dump_file_name_.c_str(), -1);
 }
 
 void DebugDumpGenerator::Process(size_t num_blocks) {
diff --git a/webrtc/modules/audio_processing/test/process_test.cc b/webrtc/modules/audio_processing/test/process_test.cc
index ae6b4dc..f54eab3 100644
--- a/webrtc/modules/audio_processing/test/process_test.cc
+++ b/webrtc/modules/audio_processing/test/process_test.cc
@@ -434,7 +434,7 @@
     } else if (strcmp(argv[i], "--debug_file") == 0) {
       i++;
       ASSERT_LT(i, argc) << "Specify filename after --debug_file";
-      ASSERT_EQ(apm->kNoError, apm->StartDebugRecording(argv[i]));
+      ASSERT_EQ(apm->kNoError, apm->StartDebugRecording(argv[i], -1));
     } else {
       FAIL() << "Unrecognized argument " << argv[i];
     }