text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <stddef.h>
  
  #include <ctime>
  
  #include "base/command_line.h"
  #include "base/files/file_enumerator.h"
  #include "base/files/file_util.h"
  #include "base/files/scoped_temp_dir.h"
  #include "base/macros.h"
  #include "base/process/launch.h"
  #include "base/process/process.h"
  #include "base/scoped_native_library.h"
  #include "base/strings/string_number_conversions.h"
  #include "base/strings/string_util.h"
  #include "base/strings/stringprintf.h"
  #include "base/strings/utf_string_conversions.h"
  #include "build/build_config.h"
  #include "chrome/browser/media/webrtc_browsertest_audio.h"
  #include "chrome/browser/media/webrtc_browsertest_base.h"
  #include "chrome/browser/media/webrtc_browsertest_common.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/ui/browser.h"
  #include "chrome/browser/ui/browser_tabstrip.h"
  #include "chrome/browser/ui/tabs/tab_strip_model.h"
  #include "chrome/common/chrome_paths.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/test/base/ui_test_utils.h"
  #include "content/public/common/content_switches.h"
  #include "content/public/test/browser_test_utils.h"
  #include "media/base/audio_parameters.h"
  #include "media/base/media_switches.h"
  #include "net/test/embedded_test_server/embedded_test_server.h"
  #include "testing/perf/perf_test.h"
  
  namespace {
  
  static const base::FilePath::CharType kReferenceFile[] =
      FILE_PATH_LITERAL("speech_44kHz_16bit_stereo.wav");
  
  // The javascript will load the reference file relative to its location,
  // which is in /webrtc on the web server. The files we are looking for are in
  // webrtc/resources in the chrome/test/data folder.
  static const char kReferenceFileRelativeUrl[] =
      "resources/speech_44kHz_16bit_stereo.wav";
  
  static const char kWebRtcAudioTestHtmlPage[] =
      "/webrtc/webrtc_audio_quality_test.html";
  
  // For the AGC test, there are 6 speech segments split on silence. If one
  // segment is significantly different in length compared to the same segment in
  // the reference file, there's something fishy going on.
  const int kMaxAgcSegmentDiffMs =
  #if defined(OS_MACOSX)
    // Something is different on Mac; http://crbug.com/477653.
    600;
  #else
    200;
  #endif
  
  #if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MACOSX)
  #define MAYBE_WebRtcAudioQualityBrowserTest WebRtcAudioQualityBrowserTest
  #else
  // Not implemented on Android, ChromeOS etc.
  #define MAYBE_WebRtcAudioQualityBrowserTest DISABLED_WebRtcAudioQualityBrowserTest
  #endif
  
  }  // namespace
  
  // Test we can set up a WebRTC call and play audio through it.
  //
  // If you're not a googler and want to run this test, you need to provide a
  // pesq binary for your platform (and sox.exe on windows). Read more on how
  // resources are managed in chrome/test/data/webrtc/resources/README.
  //
  // This test will only work on machines that have been configured to record
  // their own input.
  //
  // On Linux:
  // 1. # sudo apt-get install pavucontrol sox
  // 2. For the user who will run the test: # pavucontrol
  // 3. In a separate terminal, # arecord dummy
  // 4. In pavucontrol, go to the recording tab.
  // 5. For the ALSA plugin [aplay]: ALSA Capture from, change from <x> to
  //    <Monitor of x>, where x is whatever your primary sound device is called.
  // 6. Try launching chrome as the target user on the target machine, try
  //    playing, say, a YouTube video, and record with # arecord -f dat tmp.dat.
  //    Verify the recording with aplay (should have recorded what you played
  //    from chrome).
  //
  // Note: the volume for ALL your input devices will be forced to 100% by
  //       running this test on Linux.
  //
  // On Mac:
  // TODO(phoglund): download sox from gs instead.
  // 1. Get SoundFlower: http://rogueamoeba.com/freebies/soundflower/download.php
  // 2. Install it + reboot.
  // 3. Install MacPorts (http://www.macports.org/).
  // 4. Install sox: sudo port install sox.
  // 5. (For Chrome bots) Ensure sox and rec are reachable from the env the test
  //    executes in (sox and rec tends to install in /opt/, which generally isn't
  //    in the Chrome bots' env). For instance, run
  //    sudo ln -s /opt/local/bin/rec /usr/local/bin/rec
  //    sudo ln -s /opt/local/bin/sox /usr/local/bin/sox
  // 6. In Sound Preferences, set both input and output to Soundflower (2ch).
  //    Note: You will no longer hear audio on this machine, and it will no
  //    longer use any built-in mics.
  // 7. Try launching chrome as the target user on the target machine, try
  //    playing, say, a YouTube video, and record with 'rec test.wav trim 0 5'.
  //    Stop the video in chrome and try playing back the file; you should hear
  //    a recording of the video (note; if you play back on the target machine
  //    you must revert the changes in step 3 first).
  //
  // On Windows 7:
  // 1. Control panel > Sound > Manage audio devices.
  // 2. In the recording tab, right-click in an empty space in the pane with the
  //    devices. Tick 'show disabled devices'.
  // 3. You should see a 'stero mix' device - this is what your speakers output.
  //    Right click > Properties.
  // 4. In the Listen tab for the mix device, check the 'listen to this device'
  //    checkbox. Ensure the mix device is the default recording device.
  // 5. Launch chrome and try playing a video with sound. You should see
  //    in the volume meter for the mix device. Configure the mix device to have
  //    50 / 100 in level. Also go into the playback tab, right-click Speakers,
  //    and set that level to 50 / 100. Otherwise you will get distortion in
  //    the recording.
  class MAYBE_WebRtcAudioQualityBrowserTest : public WebRtcTestBase {
   public:
    MAYBE_WebRtcAudioQualityBrowserTest() {}
    void SetUpInProcessBrowserTestFixture() override {
      DetectErrorsInJavaScript();  // Look for errors in our rather complex js.
    }
  
    void SetUpCommandLine(base::CommandLine* command_line) override {
      EXPECT_FALSE(command_line->HasSwitch(
          switches::kUseFakeUIForMediaStream));
  
      // The WebAudio-based tests don't care what devices are available to
      // getUserMedia, and the getUserMedia-based tests will play back a file
      // through the fake device using using --use-file-for-fake-audio-capture.
      command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
  
      // Add loopback interface such that there is always connectivity.
      command_line->AppendSwitch(switches::kAllowLoopbackInPeerConnection);
    }
  
    void ConfigureFakeDeviceToPlayFile(const base::FilePath& wav_file_path) {
      base::CommandLine::ForCurrentProcess()->AppendSwitchNative(
          switches::kUseFileForFakeAudioCapture,
          wav_file_path.value() + FILE_PATH_LITERAL("%noloop"));
    }
  
    void AddAudioFileToWebAudio(const std::string& input_file_relative_url,
                                content::WebContents* tab_contents) {
      // This calls into webaudio.js.
      EXPECT_EQ("ok-added", ExecuteJavascript(
          "addAudioFile('" + input_file_relative_url + "')", tab_contents));
    }
  
    void PlayAudioFileThroughWebAudio(content::WebContents* tab_contents) {
      EXPECT_EQ("ok-playing", ExecuteJavascript("playAudioFile()", tab_contents));
    }
  
    content::WebContents* OpenPageWithoutGetUserMedia(const char* url) {
      chrome::AddTabAt(browser(), GURL(), -1, true);
      ui_test_utils::NavigateToURL(
          browser(), embedded_test_server()->GetURL(url));
      content::WebContents* tab =
          browser()->tab_strip_model()->GetActiveWebContents();
  
      // Prepare the peer connections manually in this test since we don't add
      // getUserMedia-derived media streams in this test like the other tests.
      EXPECT_EQ("ok-peerconnection-created",
                ExecuteJavascript("preparePeerConnection()", tab));
      return tab;
    }
  
    void MuteMediaElement(const std::string& element_id,
                          content::WebContents* tab_contents) {
      EXPECT_EQ("ok-muted", ExecuteJavascript(
          "setMediaElementMuted('" + element_id + "', true)", tab_contents));
    }
  
   protected:
    void TestAutoGainControl(const base::FilePath::StringType& reference_filename,
                             const std::string& constraints,
                             const std::string& perf_modifier);
    void SetupAndRecordAudioCall(const base::FilePath& reference_file,
                                 const base::FilePath& recording,
                                 const std::string& constraints,
                                 const base::TimeDelta recording_time);
    void TestWithFakeDeviceGetUserMedia(const std::string& constraints,
                                        const std::string& perf_modifier);
  };
  
  namespace {
  
  class AudioRecorder {
   public:
    AudioRecorder() {}
    ~AudioRecorder() {}
  
    // Starts the recording program for the specified duration. Returns true
    // on success. We record in 16-bit 44.1 kHz Stereo (mostly because that's
    // what SoundRecorder.exe will give us and we can't change that).
    bool StartRecording(base::TimeDelta recording_time,
                        const base::FilePath& output_file) {
      EXPECT_FALSE(recording_application_.IsValid())
          << "Tried to record, but is already recording.";
  
      int duration_sec = static_cast<int>(recording_time.InSeconds());
      base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  
  #if defined(OS_WIN)
      // This disable is required to run SoundRecorder.exe on 64-bit Windows
      // from a 32-bit binary. We need to load the wow64 disable function from
      // the DLL since it doesn't exist on Windows XP.
      base::ScopedNativeLibrary kernel32_lib(base::FilePath(L"kernel32"));
      if (kernel32_lib.is_valid()) {
        typedef BOOL (WINAPI* Wow64DisableWow64FSRedirection)(PVOID*);
        Wow64DisableWow64FSRedirection wow_64_disable_wow_64_fs_redirection;
        wow_64_disable_wow_64_fs_redirection =
            reinterpret_cast<Wow64DisableWow64FSRedirection>(
                kernel32_lib.GetFunctionPointer(
                    "Wow64DisableWow64FsRedirection"));
        if (wow_64_disable_wow_64_fs_redirection != NULL) {
          PVOID* ignored = NULL;
          wow_64_disable_wow_64_fs_redirection(ignored);
        }
      }
  
      char duration_in_hms[128] = {0};
      struct tm duration_tm = {0};
      duration_tm.tm_sec = duration_sec;
      EXPECT_NE(0u, strftime(duration_in_hms, arraysize(duration_in_hms),
                             "%H:%M:%S", &duration_tm));
  
      command_line.SetProgram(
          base::FilePath(FILE_PATH_LITERAL("SoundRecorder.exe")));
      command_line.AppendArg("/FILE");
      command_line.AppendArgPath(output_file);
      command_line.AppendArg("/DURATION");
      command_line.AppendArg(duration_in_hms);
  #elif defined(OS_MACOSX)
      command_line.SetProgram(base::FilePath("rec"));
      command_line.AppendArg("-b");
      command_line.AppendArg("16");
      command_line.AppendArg("-q");
      command_line.AppendArgPath(output_file);
      command_line.AppendArg("trim");
      command_line.AppendArg("0");
      command_line.AppendArg(base::IntToString(duration_sec));
  #else
      command_line.SetProgram(base::FilePath("arecord"));
      command_line.AppendArg("-d");
      command_line.AppendArg(base::IntToString(duration_sec));
      command_line.AppendArg("-f");
      command_line.AppendArg("cd");
      command_line.AppendArg("-c");
      command_line.AppendArg("2");
      command_line.AppendArgPath(output_file);
  #endif
  
      DVLOG(0) << "Running " << command_line.GetCommandLineString();
      recording_application_ =
          base::LaunchProcess(command_line, base::LaunchOptions());
      return recording_application_.IsValid();
    }
  
    // Joins the recording program. Returns true on success.
    bool WaitForRecordingToEnd() {
      int exit_code = -1;
      recording_application_.WaitForExit(&exit_code);
      return exit_code == 0;
    }
   private:
    base::Process recording_application_;
  };
  
  bool ForceMicrophoneVolumeTo100Percent() {
  #if defined(OS_WIN)
    // Note: the force binary isn't in tools since it's one of our own.
    base::CommandLine command_line(test::GetReferenceFilesDir().Append(
        FILE_PATH_LITERAL("force_mic_volume_max.exe")));
    DVLOG(0) << "Running " << command_line.GetCommandLineString();
    std::string result;
    if (!base::GetAppOutput(command_line, &result)) {
      LOG(ERROR) << "Failed to set source volume: output was " << result;
      return false;
    }
  #elif defined(OS_MACOSX)
    base::CommandLine command_line(
        base::FilePath(FILE_PATH_LITERAL("osascript")));
    command_line.AppendArg("-e");
    command_line.AppendArg("set volume input volume 100");
    command_line.AppendArg("-e");
    command_line.AppendArg("set volume output volume 85");
  
    std::string result;
    if (!base::GetAppOutput(command_line, &result)) {
      LOG(ERROR) << "Failed to set source volume: output was " << result;
      return false;
    }
  #else
    // Just force the volume of, say the first 5 devices. A machine will rarely
    // have more input sources than that. This is way easier than finding the
    // input device we happen to be using.
    for (int device_index = 0; device_index < 5; ++device_index) {
      std::string result;
      const std::string kHundredPercentVolume = "65536";
      base::CommandLine command_line(base::FilePath(FILE_PATH_LITERAL("pacmd")));
      command_line.AppendArg("set-source-volume");
      command_line.AppendArg(base::IntToString(device_index));
      command_line.AppendArg(kHundredPercentVolume);
      DVLOG(0) << "Running " << command_line.GetCommandLineString();
      if (!base::GetAppOutput(command_line, &result)) {
        LOG(ERROR) << "Failed to set source volume: output was " << result;
        return false;
      }
    }
  #endif
    return true;
  }
  
  // Sox is the "Swiss army knife" of audio processing. We mainly use it for
  // silence trimming. See http://sox.sourceforge.net.
  base::CommandLine MakeSoxCommandLine() {
  #if defined(OS_WIN)
    base::FilePath sox_path = test::GetToolForPlatform("sox");
    if (!base::PathExists(sox_path)) {
      LOG(ERROR) << "Missing sox.exe binary in " << sox_path.value()
                 << "; you may have to provide this binary yourself.";
      return base::CommandLine(base::CommandLine::NO_PROGRAM);
    }
    base::CommandLine command_line(sox_path);
  #else
    // TODO(phoglund): call checked-in sox rather than system sox on mac/linux.
    // Same for rec invocations on Mac, above.
    base::CommandLine command_line(base::FilePath(FILE_PATH_LITERAL("sox")));
  #endif
    return command_line;
  }
  
  // Removes silence from beginning and end of the |input_audio_file| and writes
  // the result to the |output_audio_file|. Returns true on success.
  bool RemoveSilence(const base::FilePath& input_file,
                     const base::FilePath& output_file) {
    // SOX documentation for silence command: http://sox.sourceforge.net/sox.html
    // To remove the silence from both beginning and end of the audio file, we
    // call sox silence command twice: once on normal file and again on its
    // reverse, then we reverse the final output.
    // Silence parameters are (in sequence):
    // ABOVE_PERIODS: The period for which silence occurs. Value 1 is used for
    //                 silence at beginning of audio.
    // DURATION: the amount of time in seconds that non-silence must be detected
    //           before sox stops trimming audio.
    // THRESHOLD: value used to indicate what sample value is treats as silence.
    const char* kAbovePeriods = "1";
    const char* kDuration = "2";
    const char* kTreshold = "1.5%";
  
    base::CommandLine command_line = MakeSoxCommandLine();
    if (command_line.GetProgram().empty())
      return false;
    command_line.AppendArgPath(input_file);
    command_line.AppendArgPath(output_file);
    command_line.AppendArg("silence");
    command_line.AppendArg(kAbovePeriods);
    command_line.AppendArg(kDuration);
    command_line.AppendArg(kTreshold);
    command_line.AppendArg("reverse");
    command_line.AppendArg("silence");
    command_line.AppendArg(kAbovePeriods);
    command_line.AppendArg(kDuration);
    command_line.AppendArg(kTreshold);
    command_line.AppendArg("reverse");
  
    DVLOG(0) << "Running " << command_line.GetCommandLineString();
    std::string result;
    bool ok = base::GetAppOutput(command_line, &result);
    DVLOG(0) << "Output was:\n\n" << result;
    return ok;
  }
  
  // Looks for 0.2 second audio segments surrounded by silences under 0.3% audio
  // power and splits the input file on those silences. Output files are written
  // according to the output file template (e.g. /tmp/out.wav writes
  // /tmp/out001.wav, /tmp/out002.wav, etc if there are two silence-padded
  // regions in the file). The silences between speech segments must be at
  // least 500 ms for this to be reliable.
  bool SplitFileOnSilence(const base::FilePath& input_file,
                          const base::FilePath& output_file_template) {
    base::CommandLine command_line = MakeSoxCommandLine();
    if (command_line.GetProgram().empty())
      return false;
  
    // These are experimentally determined and work on the files we use.
    const char* kAbovePeriods = "1";
    const char* kUnderPeriods = "1";
    const char* kDuration = "0.2";
    const char* kTreshold = "0.5%";
    command_line.AppendArgPath(input_file);
    command_line.AppendArgPath(output_file_template);
    command_line.AppendArg("silence");
    command_line.AppendArg(kAbovePeriods);
    command_line.AppendArg(kDuration);
    command_line.AppendArg(kTreshold);
    command_line.AppendArg(kUnderPeriods);
    command_line.AppendArg(kDuration);
    command_line.AppendArg(kTreshold);
    command_line.AppendArg(":");
    command_line.AppendArg("newfile");
    command_line.AppendArg(":");
    command_line.AppendArg("restart");
  
    DVLOG(0) << "Running " << command_line.GetCommandLineString();
    std::string result;
    bool ok = base::GetAppOutput(command_line, &result);
    DVLOG(0) << "Output was:\n\n" << result;
    return ok;
  }
  
  bool CanParseAsFloat(const std::string& value) {
    return atof(value.c_str()) != 0 || value == "0";
  }
  
  // Runs PESQ to compare |reference_file| to a |actual_file|. The |sample_rate|
  // can be either 16000 or 8000.
  //
  // PESQ is only mono-aware, so the files should preferably be recorded in mono.
  // Furthermore it expects the file to be 16 rather than 32 bits, even though
  // 32 bits might work. The audio bandwidth of the two files should be the same
  // e.g. don't compare a 32 kHz file to a 8 kHz file.
  //
  // The raw score in MOS is written to |raw_mos|, whereas the MOS-LQO score is
  // written to mos_lqo. The scores are returned as floats in string form (e.g.
  // "3.145", etc). Returns true on success.
  bool RunPesq(const base::FilePath& reference_file,
               const base::FilePath& actual_file,
               int sample_rate, std::string* raw_mos, std::string* mos_lqo) {
    // PESQ will break if the paths are too long (!).
    EXPECT_LT(reference_file.value().length(), 128u);
    EXPECT_LT(actual_file.value().length(), 128u);
  
    base::FilePath pesq_path = test::GetToolForPlatform("pesq");
    if (!base::PathExists(pesq_path)) {
      LOG(ERROR) << "Missing PESQ binary in " << pesq_path.value()
                 << "; you may have to provide this binary yourself.";
      return false;
    }
  
    base::CommandLine command_line(pesq_path);
    command_line.AppendArg(base::StringPrintf("+%d", sample_rate));
    command_line.AppendArgPath(reference_file);
    command_line.AppendArgPath(actual_file);
  
    DVLOG(0) << "Running " << command_line.GetCommandLineString();
    std::string result;
    if (!base::GetAppOutput(command_line, &result)) {
      LOG(ERROR) << "Failed to run PESQ.";
      return false;
    }
    DVLOG(0) << "Output was:\n\n" << result;
  
    const std::string result_anchor = "Prediction (Raw MOS, MOS-LQO):  = ";
    std::size_t anchor_pos = result.find(result_anchor);
    if (anchor_pos == std::string::npos) {
      LOG(ERROR) << "PESQ was not able to compute a score; we probably recorded "
          << "only silence. Please check the output/input volume levels.";
      return false;
    }
  
    // There are two tab-separated numbers on the format x.xxx, e.g. 5 chars each.
    std::size_t first_number_pos = anchor_pos + result_anchor.length();
    *raw_mos = result.substr(first_number_pos, 5);
    EXPECT_TRUE(CanParseAsFloat(*raw_mos)) << "Failed to parse raw MOS number.";
    *mos_lqo = result.substr(first_number_pos + 5 + 1, 5);
    EXPECT_TRUE(CanParseAsFloat(*mos_lqo)) << "Failed to parse MOS LQO number.";
  
    return true;
  }
  
  base::FilePath CreateTemporaryWaveFile() {
    base::FilePath filename;
    EXPECT_TRUE(base::CreateTemporaryFile(&filename));
    base::FilePath wav_filename =
        filename.AddExtension(FILE_PATH_LITERAL(".wav"));
    EXPECT_TRUE(base::Move(filename, wav_filename));
    return wav_filename;
  }
  
  void DeleteFileUnlessTestFailed(const base::FilePath& path, bool recursive) {
    if (::testing::Test::HasFailure())
      printf("Test failed; keeping recording(s) at\n\t%" PRFilePath ".\n",
             path.value().c_str());
    else
      EXPECT_TRUE(base::DeleteFile(path, recursive));
  }
  
  std::vector<base::FilePath> ListWavFilesInDir(const base::FilePath& dir) {
    base::FileEnumerator files(dir, false, base::FileEnumerator::FILES,
                               FILE_PATH_LITERAL("*.wav"));
  
    std::vector<base::FilePath> result;
    for (base::FilePath name = files.Next(); !name.empty(); name = files.Next())
      result.push_back(name);
    return result;
  }
  
  // Splits |to_split| into sub-files based on silence. The file you use must have
  // at least 500 ms periods of silence between speech segments for this to be
  // reliable.
  void SplitFileOnSilenceIntoDir(const base::FilePath& to_split,
                                 const base::FilePath& workdir) {
    // First trim beginning and end since they are tricky for the splitter.
    base::FilePath trimmed_audio = CreateTemporaryWaveFile();
  
    ASSERT_TRUE(RemoveSilence(to_split, trimmed_audio));
    DVLOG(0) << "Trimmed silence: " << trimmed_audio.value() << std::endl;
  
    ASSERT_TRUE(SplitFileOnSilence(
        trimmed_audio, workdir.Append(FILE_PATH_LITERAL("output.wav"))));
    DeleteFileUnlessTestFailed(trimmed_audio, false);
  }
  
  // Computes the difference between the actual and reference segment. A positive
  // number x means the actual file is x dB stronger than the reference.
  float AnalyzeOneSegment(const base::FilePath& ref_segment,
                          const base::FilePath& actual_segment,
                          int segment_number) {
    media::AudioParameters ref_parameters;
    media::AudioParameters actual_parameters;
    float ref_energy =
        test::ComputeAudioEnergyForWavFile(ref_segment, &ref_parameters);
    float actual_energy =
        test::ComputeAudioEnergyForWavFile(actual_segment, &actual_parameters);
  
    base::TimeDelta difference_in_length = ref_parameters.GetBufferDuration() -
                                           actual_parameters.GetBufferDuration();
  
    EXPECT_LE(difference_in_length,
              base::TimeDelta::FromMilliseconds(kMaxAgcSegmentDiffMs))
        << "Segments differ " << difference_in_length.InMilliseconds() << " ms "
        << "in length for segment " << segment_number << "; we're likely "
        << "comparing unrelated segments or silence splitting is busted.";
  
    return actual_energy - ref_energy;
  }
  
  std::string MakeTraceName(const base::FilePath& ref_filename,
                            size_t segment_number) {
    std::string ascii_filename;
  #if defined(OS_WIN)
    ascii_filename = base::WideToUTF8(ref_filename.BaseName().value());
  #else
    ascii_filename = ref_filename.BaseName().value();
  #endif
    return base::StringPrintf(
        "%s_segment_%d", ascii_filename.c_str(), (int)segment_number);
  }
  
  void AnalyzeSegmentsAndPrintResult(
      const std::vector<base::FilePath>& ref_segments,
      const std::vector<base::FilePath>& actual_segments,
      const base::FilePath& reference_file,
      const std::string& perf_modifier) {
    ASSERT_GT(ref_segments.size(), 0u)
        << "Failed to split reference file on silence; sox is likely broken.";
    ASSERT_EQ(ref_segments.size(), actual_segments.size())
        << "The recording did not result in the same number of audio segments "
        << "after on splitting on silence; WebRTC must have deformed the audio "
        << "too much.";
  
    for (size_t i = 0; i < ref_segments.size(); i++) {
      float difference_in_decibel = AnalyzeOneSegment(ref_segments[i],
                                                      actual_segments[i],
                                                      i);
      std::string trace_name = MakeTraceName(reference_file, i);
      perf_test::PrintResult("agc_energy_diff", perf_modifier, trace_name,
                             difference_in_decibel, "dB", false);
    }
  }
  
  void ComputeAndPrintPesqResults(const base::FilePath& reference_file,
                                  const base::FilePath& recording,
                                  const std::string& perf_modifier) {
    base::FilePath trimmed_reference = CreateTemporaryWaveFile();
    base::FilePath trimmed_recording = CreateTemporaryWaveFile();
  
    ASSERT_TRUE(RemoveSilence(reference_file, trimmed_reference));
    ASSERT_TRUE(RemoveSilence(recording, trimmed_recording));
  
    std::string raw_mos;
    std::string mos_lqo;
    bool succeeded = RunPesq(trimmed_reference, trimmed_recording, 16000,
                             &raw_mos, &mos_lqo);
    EXPECT_TRUE(succeeded) << "Failed to run PESQ.";
    if (succeeded) {
      perf_test::PrintResult(
          "audio_pesq", perf_modifier, "raw_mos", raw_mos, "score", true);
      perf_test::PrintResult(
          "audio_pesq", perf_modifier, "mos_lqo", mos_lqo, "score", true);
    }
  
    DeleteFileUnlessTestFailed(trimmed_reference, false);
    DeleteFileUnlessTestFailed(trimmed_recording, false);
  }
  
  }  // namespace
  
  // Sets up a two-way WebRTC call and records its output to |recording|, using
  // getUserMedia.
  //
  // |reference_file| should have at least five seconds of silence in the
  // beginning: otherwise all the reference audio will not be picked up by the
  // recording. Note that the reference file will start playing as soon as the
  // audio device is up following the getUserMedia call in the left tab. The time
  // it takes to negotiate a call isn't deterministic, but five seconds should be
  // plenty of time. Similarly, the recording time should be enough to catch the
  // whole reference file. If you then silence-trim the reference file and actual
  // file, you should end up with two time-synchronized files.
  void MAYBE_WebRtcAudioQualityBrowserTest::SetupAndRecordAudioCall(
      const base::FilePath& reference_file,
      const base::FilePath& recording,
      const std::string& constraints,
      const base::TimeDelta recording_time) {
    ASSERT_TRUE(embedded_test_server()->Start());
    ASSERT_TRUE(test::HasReferenceFilesInCheckout());
    ASSERT_TRUE(ForceMicrophoneVolumeTo100Percent());
  
    ConfigureFakeDeviceToPlayFile(reference_file);
  
    // Create a two-way call. Mute one of the receivers though; that way it will
    // be receiving audio bytes, but we will not be playing out of both elements.
    GURL test_page = embedded_test_server()->GetURL(kWebRtcAudioTestHtmlPage);
    content::WebContents* left_tab =
        OpenPageAndGetUserMediaInNewTabWithConstraints(test_page, constraints);
    SetupPeerconnectionWithLocalStream(left_tab);
    MuteMediaElement("remote-view", left_tab);
  
    content::WebContents* right_tab =
        OpenPageAndGetUserMediaInNewTabWithConstraints(test_page, constraints);
    SetupPeerconnectionWithLocalStream(right_tab);
  
    AudioRecorder recorder;
    ASSERT_TRUE(recorder.StartRecording(recording_time, recording));
  
    NegotiateCall(left_tab, right_tab);
  
    ASSERT_TRUE(recorder.WaitForRecordingToEnd());
    DVLOG(0) << "Done recording to " << recording.value() << std::endl;
  
    HangUp(left_tab);
  }
  
  void MAYBE_WebRtcAudioQualityBrowserTest::TestWithFakeDeviceGetUserMedia(
      const std::string& constraints,
      const std::string& perf_modifier) {
    if (OnWin8()) {
      // http://crbug.com/379798.
      LOG(ERROR) << "This test is not implemented for Windows XP/Win8.";
      return;
    }
  
    base::FilePath reference_file =
        test::GetReferenceFilesDir().Append(kReferenceFile);
    base::FilePath recording = CreateTemporaryWaveFile();
  
    ASSERT_NO_FATAL_FAILURE(SetupAndRecordAudioCall(
        reference_file, recording, constraints,
        base::TimeDelta::FromSeconds(30)));
  
    ComputeAndPrintPesqResults(reference_file, recording, perf_modifier);
    DeleteFileUnlessTestFailed(recording, false);
  }
  
  IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest,
                         MANUAL_TestCallQualityWithAudioFromFakeDevice) {
    TestWithFakeDeviceGetUserMedia(kAudioOnlyCallConstraints, "_getusermedia");
  }
  
  IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest,
                         MANUAL_TestCallQualityWithAudioFromWebAudio) {
    if (OnWin8()) {
      // http://crbug.com/379798.
      LOG(ERROR) << "This test is not implemented for Windows XP/Win8.";
      return;
    }
    ASSERT_TRUE(test::HasReferenceFilesInCheckout());
    ASSERT_TRUE(embedded_test_server()->Start());
  
    ASSERT_TRUE(ForceMicrophoneVolumeTo100Percent());
  
    content::WebContents* left_tab =
        OpenPageWithoutGetUserMedia(kWebRtcAudioTestHtmlPage);
    content::WebContents* right_tab =
        OpenPageWithoutGetUserMedia(kWebRtcAudioTestHtmlPage);
  
    AddAudioFileToWebAudio(kReferenceFileRelativeUrl, left_tab);
  
    NegotiateCall(left_tab, right_tab);
  
    base::FilePath recording = CreateTemporaryWaveFile();
  
    // Note: the sound clip is 21.6 seconds: record for 25 seconds to get some
    // safety margins on each side.
    AudioRecorder recorder;
    ASSERT_TRUE(recorder.StartRecording(base::TimeDelta::FromSeconds(25),
                                        recording));
  
    PlayAudioFileThroughWebAudio(left_tab);
  
    ASSERT_TRUE(recorder.WaitForRecordingToEnd());
    DVLOG(0) << "Done recording to " << recording.value() << std::endl;
  
    HangUp(left_tab);
  
    // Compare with the reference file on disk (this is the same file we played
    // through WebAudio earlier).
    base::FilePath reference_file =
        test::GetReferenceFilesDir().Append(kReferenceFile);
    ComputeAndPrintPesqResults(reference_file, recording, "_webaudio");
  }
  
  /**
   * The auto gain control test plays a file into the fake microphone. Then it
   * sets up a one-way WebRTC call with audio only and records Chrome's output on
   * the receiving side using the audio loopback provided by the quality test
   * (see the class comments for more details).
   *
   * Then both the recording and reference file are split on silence. This creates
   * a number of segments with speech in them. The reason for this is to provide
   * a kind of synchronization mechanism so the start of each speech segment is
   * compared to the start of the corresponding speech segment. This is because we
   * will experience inevitable clock drift between the system clock (which runs
   * the fake microphone) and the sound card (which runs play-out). Effectively
   * re-synchronizing on each segment mitigates this.
   *
   * The silence splitting is inherently sensitive to the sound file we run on.
   * Therefore the reference file must have at least 500 ms of pure silence
   * between speech segments; the test will fail if the output produces more
   * segments than the reference.
   *
   * The test reports the difference in decibel between the reference and output
   * file per 10 ms interval in each speech segment. A value of 6 means the
   * output was 6 dB louder than the reference, presumably because the AGC applied
   * gain to the signal.
   *
   * The test only exercises digital AGC for now.
   *
   * We record in CD format here (44.1 kHz) because that's what the fake input
   * device currently supports, and we want to be able to compare directly. See
   * http://crbug.com/421054.
   */
  void MAYBE_WebRtcAudioQualityBrowserTest::TestAutoGainControl(
      const base::FilePath::StringType& reference_filename,
      const std::string& constraints,
      const std::string& perf_modifier) {
    if (OnWin8()) {
      // http://crbug.com/379798.
      LOG(ERROR) << "This test is not implemented for Windows XP/Win8.";
      return;
    }
    base::FilePath reference_file =
        test::GetReferenceFilesDir().Append(reference_filename);
    base::FilePath recording = CreateTemporaryWaveFile();
  
    ASSERT_NO_FATAL_FAILURE(SetupAndRecordAudioCall(
        reference_file, recording, constraints,
        base::TimeDelta::FromSeconds(30)));
  
    base::ScopedTempDir split_ref_files;
    ASSERT_TRUE(split_ref_files.CreateUniqueTempDir());
    ASSERT_NO_FATAL_FAILURE(
        SplitFileOnSilenceIntoDir(reference_file, split_ref_files.path()));
    std::vector<base::FilePath> ref_segments =
        ListWavFilesInDir(split_ref_files.path());
  
    base::ScopedTempDir split_actual_files;
    ASSERT_TRUE(split_actual_files.CreateUniqueTempDir());
    ASSERT_NO_FATAL_FAILURE(
        SplitFileOnSilenceIntoDir(recording, split_actual_files.path()));
  
    // Keep the recording and split files if the analysis fails.
    base::FilePath actual_files_dir = split_actual_files.Take();
    std::vector<base::FilePath> actual_segments =
        ListWavFilesInDir(actual_files_dir);
  
    AnalyzeSegmentsAndPrintResult(
        ref_segments, actual_segments, reference_file, perf_modifier);
  
    DeleteFileUnlessTestFailed(recording, false);
    DeleteFileUnlessTestFailed(actual_files_dir, true);
  }
  
  // The AGC should apply non-zero gain here.
  IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest,
                         MANUAL_TestAutoGainControlOnLowAudio) {
    ASSERT_NO_FATAL_FAILURE(TestAutoGainControl(
        kReferenceFile, kAudioOnlyCallConstraints, "_with_agc"));
  }
  
  // Since the AGC is off here there should be no gain at all.
  IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest,
                         MANUAL_TestAutoGainIsOffWithAudioProcessingOff) {
    const char* kAudioCallWithoutAudioProcessing =
        "{audio: { mandatory: { echoCancellation: false } } }";
    ASSERT_NO_FATAL_FAILURE(TestAutoGainControl(
        kReferenceFile, kAudioCallWithoutAudioProcessing, "_no_agc"));
  }
  