1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
|
// 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"));
}
|