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

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

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

Powered by Google App Engine
This is Rietveld 408576698