Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: chrome/browser/media/chrome_webrtc_audio_quality_browsertest.cc

Issue 1762413002: Cleanup: remove prefix chrome_ of chrome/browser/media/chrome* files (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: reran the commands Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <stddef.h>
6
7 #include <ctime>
8
9 #include "base/command_line.h"
10 #include "base/files/file_enumerator.h"
11 #include "base/files/file_util.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/macros.h"
14 #include "base/process/launch.h"
15 #include "base/process/process.h"
16 #include "base/scoped_native_library.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "build/build_config.h"
22 #include "chrome/browser/media/webrtc_browsertest_audio.h"
23 #include "chrome/browser/media/webrtc_browsertest_base.h"
24 #include "chrome/browser/media/webrtc_browsertest_common.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/browser_tabstrip.h"
28 #include "chrome/browser/ui/tabs/tab_strip_model.h"
29 #include "chrome/common/chrome_paths.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/test/base/ui_test_utils.h"
32 #include "content/public/common/content_switches.h"
33 #include "content/public/test/browser_test_utils.h"
34 #include "media/audio/audio_parameters.h"
35 #include "media/base/media_switches.h"
36 #include "net/test/embedded_test_server/embedded_test_server.h"
37 #include "testing/perf/perf_test.h"
38
39 namespace {
40
41 static const base::FilePath::CharType kReferenceFile[] =
42 FILE_PATH_LITERAL("speech_44kHz_16bit_stereo.wav");
43
44 // The javascript will load the reference file relative to its location,
45 // which is in /webrtc on the web server. The files we are looking for are in
46 // webrtc/resources in the chrome/test/data folder.
47 static const char kReferenceFileRelativeUrl[] =
48 "resources/speech_44kHz_16bit_stereo.wav";
49
50 static const char kWebRtcAudioTestHtmlPage[] =
51 "/webrtc/webrtc_audio_quality_test.html";
52
53 // For the AGC test, there are 6 speech segments split on silence. If one
54 // segment is significantly different in length compared to the same segment in
55 // the reference file, there's something fishy going on.
56 const int kMaxAgcSegmentDiffMs =
57 #if defined(OS_MACOSX)
58 // Something is different on Mac; http://crbug.com/477653.
59 600;
60 #else
61 200;
62 #endif
63
64 #if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_MACOSX)
65 #define MAYBE_WebRtcAudioQualityBrowserTest WebRtcAudioQualityBrowserTest
66 #else
67 // Not implemented on Android, ChromeOS etc.
68 #define MAYBE_WebRtcAudioQualityBrowserTest DISABLED_WebRtcAudioQualityBrowserTe st
69 #endif
70
71 } // namespace
72
73 // Test we can set up a WebRTC call and play audio through it.
74 //
75 // If you're not a googler and want to run this test, you need to provide a
76 // pesq binary for your platform (and sox.exe on windows). Read more on how
77 // resources are managed in chrome/test/data/webrtc/resources/README.
78 //
79 // This test will only work on machines that have been configured to record
80 // their own input.
81 //
82 // On Linux:
83 // 1. # sudo apt-get install pavucontrol sox
84 // 2. For the user who will run the test: # pavucontrol
85 // 3. In a separate terminal, # arecord dummy
86 // 4. In pavucontrol, go to the recording tab.
87 // 5. For the ALSA plugin [aplay]: ALSA Capture from, change from <x> to
88 // <Monitor of x>, where x is whatever your primary sound device is called.
89 // 6. Try launching chrome as the target user on the target machine, try
90 // playing, say, a YouTube video, and record with # arecord -f dat tmp.dat.
91 // Verify the recording with aplay (should have recorded what you played
92 // from chrome).
93 //
94 // Note: the volume for ALL your input devices will be forced to 100% by
95 // running this test on Linux.
96 //
97 // On Mac:
98 // TODO(phoglund): download sox from gs instead.
99 // 1. Get SoundFlower: http://rogueamoeba.com/freebies/soundflower/download.php
100 // 2. Install it + reboot.
101 // 3. Install MacPorts (http://www.macports.org/).
102 // 4. Install sox: sudo port install sox.
103 // 5. (For Chrome bots) Ensure sox and rec are reachable from the env the test
104 // executes in (sox and rec tends to install in /opt/, which generally isn't
105 // in the Chrome bots' env). For instance, run
106 // sudo ln -s /opt/local/bin/rec /usr/local/bin/rec
107 // sudo ln -s /opt/local/bin/sox /usr/local/bin/sox
108 // 6. In Sound Preferences, set both input and output to Soundflower (2ch).
109 // Note: You will no longer hear audio on this machine, and it will no
110 // longer use any built-in mics.
111 // 7. Try launching chrome as the target user on the target machine, try
112 // playing, say, a YouTube video, and record with 'rec test.wav trim 0 5'.
113 // Stop the video in chrome and try playing back the file; you should hear
114 // a recording of the video (note; if you play back on the target machine
115 // you must revert the changes in step 3 first).
116 //
117 // On Windows 7:
118 // 1. Control panel > Sound > Manage audio devices.
119 // 2. In the recording tab, right-click in an empty space in the pane with the
120 // devices. Tick 'show disabled devices'.
121 // 3. You should see a 'stero mix' device - this is what your speakers output.
122 // Right click > Properties.
123 // 4. In the Listen tab for the mix device, check the 'listen to this device'
124 // checkbox. Ensure the mix device is the default recording device.
125 // 5. Launch chrome and try playing a video with sound. You should see
126 // in the volume meter for the mix device. Configure the mix device to have
127 // 50 / 100 in level. Also go into the playback tab, right-click Speakers,
128 // and set that level to 50 / 100. Otherwise you will get distortion in
129 // the recording.
130 class MAYBE_WebRtcAudioQualityBrowserTest : public WebRtcTestBase {
131 public:
132 MAYBE_WebRtcAudioQualityBrowserTest() {}
133 void SetUpInProcessBrowserTestFixture() override {
134 DetectErrorsInJavaScript(); // Look for errors in our rather complex js.
135 }
136
137 void SetUpCommandLine(base::CommandLine* command_line) override {
138 EXPECT_FALSE(command_line->HasSwitch(
139 switches::kUseFakeUIForMediaStream));
140
141 // The WebAudio-based tests don't care what devices are available to
142 // getUserMedia, and the getUserMedia-based tests will play back a file
143 // through the fake device using using --use-file-for-fake-audio-capture.
144 command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
145
146 // Add loopback interface such that there is always connectivity.
147 command_line->AppendSwitch(switches::kAllowLoopbackInPeerConnection);
148 }
149
150 void ConfigureFakeDeviceToPlayFile(const base::FilePath& wav_file_path) {
151 base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
152 switches::kUseFileForFakeAudioCapture, wav_file_path);
153 }
154
155 void AddAudioFileToWebAudio(const std::string& input_file_relative_url,
156 content::WebContents* tab_contents) {
157 // This calls into webaudio.js.
158 EXPECT_EQ("ok-added", ExecuteJavascript(
159 "addAudioFile('" + input_file_relative_url + "')", tab_contents));
160 }
161
162 void PlayAudioFileThroughWebAudio(content::WebContents* tab_contents) {
163 EXPECT_EQ("ok-playing", ExecuteJavascript("playAudioFile()", tab_contents));
164 }
165
166 content::WebContents* OpenPageWithoutGetUserMedia(const char* url) {
167 chrome::AddTabAt(browser(), GURL(), -1, true);
168 ui_test_utils::NavigateToURL(
169 browser(), embedded_test_server()->GetURL(url));
170 content::WebContents* tab =
171 browser()->tab_strip_model()->GetActiveWebContents();
172
173 // Prepare the peer connections manually in this test since we don't add
174 // getUserMedia-derived media streams in this test like the other tests.
175 EXPECT_EQ("ok-peerconnection-created",
176 ExecuteJavascript("preparePeerConnection()", tab));
177 return tab;
178 }
179
180 void MuteMediaElement(const std::string& element_id,
181 content::WebContents* tab_contents) {
182 EXPECT_EQ("ok-muted", ExecuteJavascript(
183 "setMediaElementMuted('" + element_id + "', true)", tab_contents));
184 }
185
186 protected:
187 void TestAutoGainControl(const base::FilePath::StringType& reference_filename,
188 const std::string& constraints,
189 const std::string& perf_modifier);
190 void SetupAndRecordAudioCall(const base::FilePath& reference_file,
191 const base::FilePath& recording,
192 const std::string& constraints,
193 const base::TimeDelta recording_time);
194 void TestWithFakeDeviceGetUserMedia(const std::string& constraints,
195 const std::string& perf_modifier);
196 };
197
198 namespace {
199
200 class AudioRecorder {
201 public:
202 AudioRecorder() {}
203 ~AudioRecorder() {}
204
205 // Starts the recording program for the specified duration. Returns true
206 // on success. We record in 16-bit 44.1 kHz Stereo (mostly because that's
207 // what SoundRecorder.exe will give us and we can't change that).
208 bool StartRecording(base::TimeDelta recording_time,
209 const base::FilePath& output_file) {
210 EXPECT_FALSE(recording_application_.IsValid())
211 << "Tried to record, but is already recording.";
212
213 int duration_sec = static_cast<int>(recording_time.InSeconds());
214 base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
215
216 #if defined(OS_WIN)
217 // This disable is required to run SoundRecorder.exe on 64-bit Windows
218 // from a 32-bit binary. We need to load the wow64 disable function from
219 // the DLL since it doesn't exist on Windows XP.
220 base::ScopedNativeLibrary kernel32_lib(base::FilePath(L"kernel32"));
221 if (kernel32_lib.is_valid()) {
222 typedef BOOL (WINAPI* Wow64DisableWow64FSRedirection)(PVOID*);
223 Wow64DisableWow64FSRedirection wow_64_disable_wow_64_fs_redirection;
224 wow_64_disable_wow_64_fs_redirection =
225 reinterpret_cast<Wow64DisableWow64FSRedirection>(
226 kernel32_lib.GetFunctionPointer(
227 "Wow64DisableWow64FsRedirection"));
228 if (wow_64_disable_wow_64_fs_redirection != NULL) {
229 PVOID* ignored = NULL;
230 wow_64_disable_wow_64_fs_redirection(ignored);
231 }
232 }
233
234 char duration_in_hms[128] = {0};
235 struct tm duration_tm = {0};
236 duration_tm.tm_sec = duration_sec;
237 EXPECT_NE(0u, strftime(duration_in_hms, arraysize(duration_in_hms),
238 "%H:%M:%S", &duration_tm));
239
240 command_line.SetProgram(
241 base::FilePath(FILE_PATH_LITERAL("SoundRecorder.exe")));
242 command_line.AppendArg("/FILE");
243 command_line.AppendArgPath(output_file);
244 command_line.AppendArg("/DURATION");
245 command_line.AppendArg(duration_in_hms);
246 #elif defined(OS_MACOSX)
247 command_line.SetProgram(base::FilePath("rec"));
248 command_line.AppendArg("-b");
249 command_line.AppendArg("16");
250 command_line.AppendArg("-q");
251 command_line.AppendArgPath(output_file);
252 command_line.AppendArg("trim");
253 command_line.AppendArg("0");
254 command_line.AppendArg(base::IntToString(duration_sec));
255 #else
256 command_line.SetProgram(base::FilePath("arecord"));
257 command_line.AppendArg("-d");
258 command_line.AppendArg(base::IntToString(duration_sec));
259 command_line.AppendArg("-f");
260 command_line.AppendArg("cd");
261 command_line.AppendArg("-c");
262 command_line.AppendArg("2");
263 command_line.AppendArgPath(output_file);
264 #endif
265
266 DVLOG(0) << "Running " << command_line.GetCommandLineString();
267 recording_application_ =
268 base::LaunchProcess(command_line, base::LaunchOptions());
269 return recording_application_.IsValid();
270 }
271
272 // Joins the recording program. Returns true on success.
273 bool WaitForRecordingToEnd() {
274 int exit_code = -1;
275 recording_application_.WaitForExit(&exit_code);
276 return exit_code == 0;
277 }
278 private:
279 base::Process recording_application_;
280 };
281
282 bool ForceMicrophoneVolumeTo100Percent() {
283 #if defined(OS_WIN)
284 // Note: the force binary isn't in tools since it's one of our own.
285 base::CommandLine command_line(test::GetReferenceFilesDir().Append(
286 FILE_PATH_LITERAL("force_mic_volume_max.exe")));
287 DVLOG(0) << "Running " << command_line.GetCommandLineString();
288 std::string result;
289 if (!base::GetAppOutput(command_line, &result)) {
290 LOG(ERROR) << "Failed to set source volume: output was " << result;
291 return false;
292 }
293 #elif defined(OS_MACOSX)
294 base::CommandLine command_line(
295 base::FilePath(FILE_PATH_LITERAL("osascript")));
296 command_line.AppendArg("-e");
297 command_line.AppendArg("set volume input volume 100");
298 command_line.AppendArg("-e");
299 command_line.AppendArg("set volume output volume 85");
300
301 std::string result;
302 if (!base::GetAppOutput(command_line, &result)) {
303 LOG(ERROR) << "Failed to set source volume: output was " << result;
304 return false;
305 }
306 #else
307 // Just force the volume of, say the first 5 devices. A machine will rarely
308 // have more input sources than that. This is way easier than finding the
309 // input device we happen to be using.
310 for (int device_index = 0; device_index < 5; ++device_index) {
311 std::string result;
312 const std::string kHundredPercentVolume = "65536";
313 base::CommandLine command_line(base::FilePath(FILE_PATH_LITERAL("pacmd")));
314 command_line.AppendArg("set-source-volume");
315 command_line.AppendArg(base::IntToString(device_index));
316 command_line.AppendArg(kHundredPercentVolume);
317 DVLOG(0) << "Running " << command_line.GetCommandLineString();
318 if (!base::GetAppOutput(command_line, &result)) {
319 LOG(ERROR) << "Failed to set source volume: output was " << result;
320 return false;
321 }
322 }
323 #endif
324 return true;
325 }
326
327 // Sox is the "Swiss army knife" of audio processing. We mainly use it for
328 // silence trimming. See http://sox.sourceforge.net.
329 base::CommandLine MakeSoxCommandLine() {
330 #if defined(OS_WIN)
331 base::FilePath sox_path = test::GetToolForPlatform("sox");
332 if (!base::PathExists(sox_path)) {
333 LOG(ERROR) << "Missing sox.exe binary in " << sox_path.value()
334 << "; you may have to provide this binary yourself.";
335 return base::CommandLine(base::CommandLine::NO_PROGRAM);
336 }
337 base::CommandLine command_line(sox_path);
338 #else
339 // TODO(phoglund): call checked-in sox rather than system sox on mac/linux.
340 // Same for rec invocations on Mac, above.
341 base::CommandLine command_line(base::FilePath(FILE_PATH_LITERAL("sox")));
342 #endif
343 return command_line;
344 }
345
346 // Removes silence from beginning and end of the |input_audio_file| and writes
347 // the result to the |output_audio_file|. Returns true on success.
348 bool RemoveSilence(const base::FilePath& input_file,
349 const base::FilePath& output_file) {
350 // SOX documentation for silence command: http://sox.sourceforge.net/sox.html
351 // To remove the silence from both beginning and end of the audio file, we
352 // call sox silence command twice: once on normal file and again on its
353 // reverse, then we reverse the final output.
354 // Silence parameters are (in sequence):
355 // ABOVE_PERIODS: The period for which silence occurs. Value 1 is used for
356 // silence at beginning of audio.
357 // DURATION: the amount of time in seconds that non-silence must be detected
358 // before sox stops trimming audio.
359 // THRESHOLD: value used to indicate what sample value is treats as silence.
360 const char* kAbovePeriods = "1";
361 const char* kDuration = "2";
362 const char* kTreshold = "1.5%";
363
364 base::CommandLine command_line = MakeSoxCommandLine();
365 if (command_line.GetProgram().empty())
366 return false;
367 command_line.AppendArgPath(input_file);
368 command_line.AppendArgPath(output_file);
369 command_line.AppendArg("silence");
370 command_line.AppendArg(kAbovePeriods);
371 command_line.AppendArg(kDuration);
372 command_line.AppendArg(kTreshold);
373 command_line.AppendArg("reverse");
374 command_line.AppendArg("silence");
375 command_line.AppendArg(kAbovePeriods);
376 command_line.AppendArg(kDuration);
377 command_line.AppendArg(kTreshold);
378 command_line.AppendArg("reverse");
379
380 DVLOG(0) << "Running " << command_line.GetCommandLineString();
381 std::string result;
382 bool ok = base::GetAppOutput(command_line, &result);
383 DVLOG(0) << "Output was:\n\n" << result;
384 return ok;
385 }
386
387 // Looks for 0.2 second audio segments surrounded by silences under 0.3% audio
388 // power and splits the input file on those silences. Output files are written
389 // according to the output file template (e.g. /tmp/out.wav writes
390 // /tmp/out001.wav, /tmp/out002.wav, etc if there are two silence-padded
391 // regions in the file). The silences between speech segments must be at
392 // least 500 ms for this to be reliable.
393 bool SplitFileOnSilence(const base::FilePath& input_file,
394 const base::FilePath& output_file_template) {
395 base::CommandLine command_line = MakeSoxCommandLine();
396 if (command_line.GetProgram().empty())
397 return false;
398
399 // These are experimentally determined and work on the files we use.
400 const char* kAbovePeriods = "1";
401 const char* kUnderPeriods = "1";
402 const char* kDuration = "0.2";
403 const char* kTreshold = "0.5%";
404 command_line.AppendArgPath(input_file);
405 command_line.AppendArgPath(output_file_template);
406 command_line.AppendArg("silence");
407 command_line.AppendArg(kAbovePeriods);
408 command_line.AppendArg(kDuration);
409 command_line.AppendArg(kTreshold);
410 command_line.AppendArg(kUnderPeriods);
411 command_line.AppendArg(kDuration);
412 command_line.AppendArg(kTreshold);
413 command_line.AppendArg(":");
414 command_line.AppendArg("newfile");
415 command_line.AppendArg(":");
416 command_line.AppendArg("restart");
417
418 DVLOG(0) << "Running " << command_line.GetCommandLineString();
419 std::string result;
420 bool ok = base::GetAppOutput(command_line, &result);
421 DVLOG(0) << "Output was:\n\n" << result;
422 return ok;
423 }
424
425 bool CanParseAsFloat(const std::string& value) {
426 return atof(value.c_str()) != 0 || value == "0";
427 }
428
429 // Runs PESQ to compare |reference_file| to a |actual_file|. The |sample_rate|
430 // can be either 16000 or 8000.
431 //
432 // PESQ is only mono-aware, so the files should preferably be recorded in mono.
433 // Furthermore it expects the file to be 16 rather than 32 bits, even though
434 // 32 bits might work. The audio bandwidth of the two files should be the same
435 // e.g. don't compare a 32 kHz file to a 8 kHz file.
436 //
437 // The raw score in MOS is written to |raw_mos|, whereas the MOS-LQO score is
438 // written to mos_lqo. The scores are returned as floats in string form (e.g.
439 // "3.145", etc). Returns true on success.
440 bool RunPesq(const base::FilePath& reference_file,
441 const base::FilePath& actual_file,
442 int sample_rate, std::string* raw_mos, std::string* mos_lqo) {
443 // PESQ will break if the paths are too long (!).
444 EXPECT_LT(reference_file.value().length(), 128u);
445 EXPECT_LT(actual_file.value().length(), 128u);
446
447 base::FilePath pesq_path = test::GetToolForPlatform("pesq");
448 if (!base::PathExists(pesq_path)) {
449 LOG(ERROR) << "Missing PESQ binary in " << pesq_path.value()
450 << "; you may have to provide this binary yourself.";
451 return false;
452 }
453
454 base::CommandLine command_line(pesq_path);
455 command_line.AppendArg(base::StringPrintf("+%d", sample_rate));
456 command_line.AppendArgPath(reference_file);
457 command_line.AppendArgPath(actual_file);
458
459 DVLOG(0) << "Running " << command_line.GetCommandLineString();
460 std::string result;
461 if (!base::GetAppOutput(command_line, &result)) {
462 LOG(ERROR) << "Failed to run PESQ.";
463 return false;
464 }
465 DVLOG(0) << "Output was:\n\n" << result;
466
467 const std::string result_anchor = "Prediction (Raw MOS, MOS-LQO): = ";
468 std::size_t anchor_pos = result.find(result_anchor);
469 if (anchor_pos == std::string::npos) {
470 LOG(ERROR) << "PESQ was not able to compute a score; we probably recorded "
471 << "only silence. Please check the output/input volume levels.";
472 return false;
473 }
474
475 // There are two tab-separated numbers on the format x.xxx, e.g. 5 chars each.
476 std::size_t first_number_pos = anchor_pos + result_anchor.length();
477 *raw_mos = result.substr(first_number_pos, 5);
478 EXPECT_TRUE(CanParseAsFloat(*raw_mos)) << "Failed to parse raw MOS number.";
479 *mos_lqo = result.substr(first_number_pos + 5 + 1, 5);
480 EXPECT_TRUE(CanParseAsFloat(*mos_lqo)) << "Failed to parse MOS LQO number.";
481
482 return true;
483 }
484
485 base::FilePath CreateTemporaryWaveFile() {
486 base::FilePath filename;
487 EXPECT_TRUE(base::CreateTemporaryFile(&filename));
488 base::FilePath wav_filename =
489 filename.AddExtension(FILE_PATH_LITERAL(".wav"));
490 EXPECT_TRUE(base::Move(filename, wav_filename));
491 return wav_filename;
492 }
493
494 void DeleteFileUnlessTestFailed(const base::FilePath& path, bool recursive) {
495 if (::testing::Test::HasFailure())
496 printf("Test failed; keeping recording(s) at\n\t%" PRFilePath ".\n",
497 path.value().c_str());
498 else
499 EXPECT_TRUE(base::DeleteFile(path, recursive));
500 }
501
502 std::vector<base::FilePath> ListWavFilesInDir(const base::FilePath& dir) {
503 base::FileEnumerator files(dir, false, base::FileEnumerator::FILES,
504 FILE_PATH_LITERAL("*.wav"));
505
506 std::vector<base::FilePath> result;
507 for (base::FilePath name = files.Next(); !name.empty(); name = files.Next())
508 result.push_back(name);
509 return result;
510 }
511
512 // Splits |to_split| into sub-files based on silence. The file you use must have
513 // at least 500 ms periods of silence between speech segments for this to be
514 // reliable.
515 void SplitFileOnSilenceIntoDir(const base::FilePath& to_split,
516 const base::FilePath& workdir) {
517 // First trim beginning and end since they are tricky for the splitter.
518 base::FilePath trimmed_audio = CreateTemporaryWaveFile();
519
520 ASSERT_TRUE(RemoveSilence(to_split, trimmed_audio));
521 DVLOG(0) << "Trimmed silence: " << trimmed_audio.value() << std::endl;
522
523 ASSERT_TRUE(SplitFileOnSilence(
524 trimmed_audio, workdir.Append(FILE_PATH_LITERAL("output.wav"))));
525 DeleteFileUnlessTestFailed(trimmed_audio, false);
526 }
527
528 // Computes the difference between the actual and reference segment. A positive
529 // number x means the actual file is x dB stronger than the reference.
530 float AnalyzeOneSegment(const base::FilePath& ref_segment,
531 const base::FilePath& actual_segment,
532 int segment_number) {
533 media::AudioParameters ref_parameters;
534 media::AudioParameters actual_parameters;
535 float ref_energy =
536 test::ComputeAudioEnergyForWavFile(ref_segment, &ref_parameters);
537 float actual_energy =
538 test::ComputeAudioEnergyForWavFile(actual_segment, &actual_parameters);
539
540 base::TimeDelta difference_in_length = ref_parameters.GetBufferDuration() -
541 actual_parameters.GetBufferDuration();
542
543 EXPECT_LE(difference_in_length,
544 base::TimeDelta::FromMilliseconds(kMaxAgcSegmentDiffMs))
545 << "Segments differ " << difference_in_length.InMilliseconds() << " ms "
546 << "in length for segment " << segment_number << "; we're likely "
547 << "comparing unrelated segments or silence splitting is busted.";
548
549 return actual_energy - ref_energy;
550 }
551
552 std::string MakeTraceName(const base::FilePath& ref_filename,
553 size_t segment_number) {
554 std::string ascii_filename;
555 #if defined(OS_WIN)
556 ascii_filename = base::WideToUTF8(ref_filename.BaseName().value());
557 #else
558 ascii_filename = ref_filename.BaseName().value();
559 #endif
560 return base::StringPrintf(
561 "%s_segment_%d", ascii_filename.c_str(), (int)segment_number);
562 }
563
564 void AnalyzeSegmentsAndPrintResult(
565 const std::vector<base::FilePath>& ref_segments,
566 const std::vector<base::FilePath>& actual_segments,
567 const base::FilePath& reference_file,
568 const std::string& perf_modifier) {
569 ASSERT_GT(ref_segments.size(), 0u)
570 << "Failed to split reference file on silence; sox is likely broken.";
571 ASSERT_EQ(ref_segments.size(), actual_segments.size())
572 << "The recording did not result in the same number of audio segments "
573 << "after on splitting on silence; WebRTC must have deformed the audio "
574 << "too much.";
575
576 for (size_t i = 0; i < ref_segments.size(); i++) {
577 float difference_in_decibel = AnalyzeOneSegment(ref_segments[i],
578 actual_segments[i],
579 i);
580 std::string trace_name = MakeTraceName(reference_file, i);
581 perf_test::PrintResult("agc_energy_diff", perf_modifier, trace_name,
582 difference_in_decibel, "dB", false);
583 }
584 }
585
586 void ComputeAndPrintPesqResults(const base::FilePath& reference_file,
587 const base::FilePath& recording,
588 const std::string& perf_modifier) {
589 base::FilePath trimmed_reference = CreateTemporaryWaveFile();
590 base::FilePath trimmed_recording = CreateTemporaryWaveFile();
591
592 ASSERT_TRUE(RemoveSilence(reference_file, trimmed_reference));
593 ASSERT_TRUE(RemoveSilence(recording, trimmed_recording));
594
595 std::string raw_mos;
596 std::string mos_lqo;
597 bool succeeded = RunPesq(trimmed_reference, trimmed_recording, 16000,
598 &raw_mos, &mos_lqo);
599 EXPECT_TRUE(succeeded) << "Failed to run PESQ.";
600 if (succeeded) {
601 perf_test::PrintResult(
602 "audio_pesq", perf_modifier, "raw_mos", raw_mos, "score", true);
603 perf_test::PrintResult(
604 "audio_pesq", perf_modifier, "mos_lqo", mos_lqo, "score", true);
605 }
606
607 DeleteFileUnlessTestFailed(trimmed_reference, false);
608 DeleteFileUnlessTestFailed(trimmed_recording, false);
609 }
610
611 } // namespace
612
613 // Sets up a two-way WebRTC call and records its output to |recording|, using
614 // getUserMedia.
615 //
616 // |reference_file| should have at least five seconds of silence in the
617 // beginning: otherwise all the reference audio will not be picked up by the
618 // recording. Note that the reference file will start playing as soon as the
619 // audio device is up following the getUserMedia call in the left tab. The time
620 // it takes to negotiate a call isn't deterministic, but five seconds should be
621 // plenty of time. Similarly, the recording time should be enough to catch the
622 // whole reference file. If you then silence-trim the reference file and actual
623 // file, you should end up with two time-synchronized files.
624 void MAYBE_WebRtcAudioQualityBrowserTest::SetupAndRecordAudioCall(
625 const base::FilePath& reference_file,
626 const base::FilePath& recording,
627 const std::string& constraints,
628 const base::TimeDelta recording_time) {
629 ASSERT_TRUE(embedded_test_server()->Start());
630 ASSERT_TRUE(test::HasReferenceFilesInCheckout());
631 ASSERT_TRUE(ForceMicrophoneVolumeTo100Percent());
632
633 ConfigureFakeDeviceToPlayFile(reference_file);
634
635 // Create a two-way call. Mute one of the receivers though; that way it will
636 // be receiving audio bytes, but we will not be playing out of both elements.
637 GURL test_page = embedded_test_server()->GetURL(kWebRtcAudioTestHtmlPage);
638 content::WebContents* left_tab =
639 OpenPageAndGetUserMediaInNewTabWithConstraints(test_page, constraints);
640 SetupPeerconnectionWithLocalStream(left_tab);
641 MuteMediaElement("remote-view", left_tab);
642
643 content::WebContents* right_tab =
644 OpenPageAndGetUserMediaInNewTabWithConstraints(test_page, constraints);
645 SetupPeerconnectionWithLocalStream(right_tab);
646
647 AudioRecorder recorder;
648 ASSERT_TRUE(recorder.StartRecording(recording_time, recording));
649
650 NegotiateCall(left_tab, right_tab);
651
652 ASSERT_TRUE(recorder.WaitForRecordingToEnd());
653 DVLOG(0) << "Done recording to " << recording.value() << std::endl;
654
655 HangUp(left_tab);
656 }
657
658 void MAYBE_WebRtcAudioQualityBrowserTest::TestWithFakeDeviceGetUserMedia(
659 const std::string& constraints,
660 const std::string& perf_modifier) {
661 if (OnWin8()) {
662 // http://crbug.com/379798.
663 LOG(ERROR) << "This test is not implemented for Windows XP/Win8.";
664 return;
665 }
666
667 base::FilePath reference_file =
668 test::GetReferenceFilesDir().Append(kReferenceFile);
669 base::FilePath recording = CreateTemporaryWaveFile();
670
671 ASSERT_NO_FATAL_FAILURE(SetupAndRecordAudioCall(
672 reference_file, recording, constraints,
673 base::TimeDelta::FromSeconds(30)));
674
675 ComputeAndPrintPesqResults(reference_file, recording, perf_modifier);
676 DeleteFileUnlessTestFailed(recording, false);
677 }
678
679 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest,
680 MANUAL_TestCallQualityWithAudioFromFakeDevice) {
681 TestWithFakeDeviceGetUserMedia(kAudioOnlyCallConstraints, "_getusermedia");
682 }
683
684 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest,
685 MANUAL_TestCallQualityWithAudioFromWebAudio) {
686 if (OnWin8()) {
687 // http://crbug.com/379798.
688 LOG(ERROR) << "This test is not implemented for Windows XP/Win8.";
689 return;
690 }
691 ASSERT_TRUE(test::HasReferenceFilesInCheckout());
692 ASSERT_TRUE(embedded_test_server()->Start());
693
694 ASSERT_TRUE(ForceMicrophoneVolumeTo100Percent());
695
696 content::WebContents* left_tab =
697 OpenPageWithoutGetUserMedia(kWebRtcAudioTestHtmlPage);
698 content::WebContents* right_tab =
699 OpenPageWithoutGetUserMedia(kWebRtcAudioTestHtmlPage);
700
701 AddAudioFileToWebAudio(kReferenceFileRelativeUrl, left_tab);
702
703 NegotiateCall(left_tab, right_tab);
704
705 base::FilePath recording = CreateTemporaryWaveFile();
706
707 // Note: the sound clip is 21.6 seconds: record for 25 seconds to get some
708 // safety margins on each side.
709 AudioRecorder recorder;
710 ASSERT_TRUE(recorder.StartRecording(base::TimeDelta::FromSeconds(25),
711 recording));
712
713 PlayAudioFileThroughWebAudio(left_tab);
714
715 ASSERT_TRUE(recorder.WaitForRecordingToEnd());
716 DVLOG(0) << "Done recording to " << recording.value() << std::endl;
717
718 HangUp(left_tab);
719
720 // Compare with the reference file on disk (this is the same file we played
721 // through WebAudio earlier).
722 base::FilePath reference_file =
723 test::GetReferenceFilesDir().Append(kReferenceFile);
724 ComputeAndPrintPesqResults(reference_file, recording, "_webaudio");
725 }
726
727 /**
728 * The auto gain control test plays a file into the fake microphone. Then it
729 * sets up a one-way WebRTC call with audio only and records Chrome's output on
730 * the receiving side using the audio loopback provided by the quality test
731 * (see the class comments for more details).
732 *
733 * Then both the recording and reference file are split on silence. This creates
734 * a number of segments with speech in them. The reason for this is to provide
735 * a kind of synchronization mechanism so the start of each speech segment is
736 * compared to the start of the corresponding speech segment. This is because we
737 * will experience inevitable clock drift between the system clock (which runs
738 * the fake microphone) and the sound card (which runs play-out). Effectively
739 * re-synchronizing on each segment mitigates this.
740 *
741 * The silence splitting is inherently sensitive to the sound file we run on.
742 * Therefore the reference file must have at least 500 ms of pure silence
743 * between speech segments; the test will fail if the output produces more
744 * segments than the reference.
745 *
746 * The test reports the difference in decibel between the reference and output
747 * file per 10 ms interval in each speech segment. A value of 6 means the
748 * output was 6 dB louder than the reference, presumably because the AGC applied
749 * gain to the signal.
750 *
751 * The test only exercises digital AGC for now.
752 *
753 * We record in CD format here (44.1 kHz) because that's what the fake input
754 * device currently supports, and we want to be able to compare directly. See
755 * http://crbug.com/421054.
756 */
757 void MAYBE_WebRtcAudioQualityBrowserTest::TestAutoGainControl(
758 const base::FilePath::StringType& reference_filename,
759 const std::string& constraints,
760 const std::string& perf_modifier) {
761 if (OnWin8()) {
762 // http://crbug.com/379798.
763 LOG(ERROR) << "This test is not implemented for Windows XP/Win8.";
764 return;
765 }
766 base::FilePath reference_file =
767 test::GetReferenceFilesDir().Append(reference_filename);
768 base::FilePath recording = CreateTemporaryWaveFile();
769
770 ASSERT_NO_FATAL_FAILURE(SetupAndRecordAudioCall(
771 reference_file, recording, constraints,
772 base::TimeDelta::FromSeconds(30)));
773
774 base::ScopedTempDir split_ref_files;
775 ASSERT_TRUE(split_ref_files.CreateUniqueTempDir());
776 ASSERT_NO_FATAL_FAILURE(
777 SplitFileOnSilenceIntoDir(reference_file, split_ref_files.path()));
778 std::vector<base::FilePath> ref_segments =
779 ListWavFilesInDir(split_ref_files.path());
780
781 base::ScopedTempDir split_actual_files;
782 ASSERT_TRUE(split_actual_files.CreateUniqueTempDir());
783 ASSERT_NO_FATAL_FAILURE(
784 SplitFileOnSilenceIntoDir(recording, split_actual_files.path()));
785
786 // Keep the recording and split files if the analysis fails.
787 base::FilePath actual_files_dir = split_actual_files.Take();
788 std::vector<base::FilePath> actual_segments =
789 ListWavFilesInDir(actual_files_dir);
790
791 AnalyzeSegmentsAndPrintResult(
792 ref_segments, actual_segments, reference_file, perf_modifier);
793
794 DeleteFileUnlessTestFailed(recording, false);
795 DeleteFileUnlessTestFailed(actual_files_dir, true);
796 }
797
798 // The AGC should apply non-zero gain here.
799 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest,
800 MANUAL_TestAutoGainControlOnLowAudio) {
801 ASSERT_NO_FATAL_FAILURE(TestAutoGainControl(
802 kReferenceFile, kAudioOnlyCallConstraints, "_with_agc"));
803 }
804
805 // Since the AGC is off here there should be no gain at all.
806 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest,
807 MANUAL_TestAutoGainIsOffWithAudioProcessingOff) {
808 const char* kAudioCallWithoutAudioProcessing =
809 "{audio: { mandatory: { echoCancellation: false } } }";
810 ASSERT_NO_FATAL_FAILURE(TestAutoGainControl(
811 kReferenceFile, kAudioCallWithoutAudioProcessing, "_no_agc"));
812 }
OLDNEW
« no previous file with comments | « chrome/browser/media/chrome_webrtc_apprtc_browsertest.cc ('k') | chrome/browser/media/chrome_webrtc_browsertest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698