| Index: chrome/browser/media/chrome_webrtc_audio_quality_browsertest.cc
|
| diff --git a/chrome/browser/media/chrome_webrtc_audio_quality_browsertest.cc b/chrome/browser/media/chrome_webrtc_audio_quality_browsertest.cc
|
| deleted file mode 100644
|
| index 2c2a8687eb91cbfa0dfa53232879a8fd5d89ddf9..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/media/chrome_webrtc_audio_quality_browsertest.cc
|
| +++ /dev/null
|
| @@ -1,812 +0,0 @@
|
| -// 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/audio/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()->AppendSwitchPath(
|
| - switches::kUseFileForFakeAudioCapture, wav_file_path);
|
| - }
|
| -
|
| - 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"));
|
| -}
|
|
|