Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <ctime> | 5 #include <ctime> |
| 6 | 6 |
| 7 #include "base/command_line.h" | 7 #include "base/command_line.h" |
| 8 #include "base/files/file_enumerator.h" | |
| 8 #include "base/files/file_util.h" | 9 #include "base/files/file_util.h" |
| 10 #include "base/files/scoped_temp_dir.h" | |
| 9 #include "base/process/launch.h" | 11 #include "base/process/launch.h" |
| 10 #include "base/process/process.h" | 12 #include "base/process/process.h" |
| 11 #include "base/scoped_native_library.h" | 13 #include "base/scoped_native_library.h" |
| 14 #include "base/strings/string_util.h" | |
| 12 #include "base/strings/stringprintf.h" | 15 #include "base/strings/stringprintf.h" |
| 16 #include "chrome/browser/media/webrtc_browsertest_audio.h" | |
| 13 #include "chrome/browser/media/webrtc_browsertest_base.h" | 17 #include "chrome/browser/media/webrtc_browsertest_base.h" |
| 14 #include "chrome/browser/media/webrtc_browsertest_common.h" | 18 #include "chrome/browser/media/webrtc_browsertest_common.h" |
| 15 #include "chrome/browser/profiles/profile.h" | 19 #include "chrome/browser/profiles/profile.h" |
| 16 #include "chrome/browser/ui/browser.h" | 20 #include "chrome/browser/ui/browser.h" |
| 17 #include "chrome/browser/ui/browser_tabstrip.h" | 21 #include "chrome/browser/ui/browser_tabstrip.h" |
| 18 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 22 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 19 #include "chrome/common/chrome_paths.h" | 23 #include "chrome/common/chrome_paths.h" |
| 20 #include "chrome/common/chrome_switches.h" | 24 #include "chrome/common/chrome_switches.h" |
| 21 #include "chrome/test/base/ui_test_utils.h" | 25 #include "chrome/test/base/ui_test_utils.h" |
| 22 #include "content/public/test/browser_test_utils.h" | 26 #include "content/public/test/browser_test_utils.h" |
| 27 #include "media/audio/audio_parameters.h" | |
| 23 #include "media/base/media_switches.h" | 28 #include "media/base/media_switches.h" |
| 24 #include "net/test/embedded_test_server/embedded_test_server.h" | 29 #include "net/test/embedded_test_server/embedded_test_server.h" |
| 25 #include "testing/perf/perf_test.h" | 30 #include "testing/perf/perf_test.h" |
| 26 | 31 |
| 27 // These are relative to the reference file dir defined by | 32 // These are relative to the reference file dir defined by |
| 28 // webrtc_browsertest_common.h (i.e. chrome/test/data/webrtc/resources). | 33 // webrtc_browsertest_common.h (i.e. chrome/test/data/webrtc/resources). |
| 29 static const base::FilePath::CharType kReferenceFile[] = | 34 static const base::FilePath::CharType kReferenceFile[] = |
| 30 #if defined (OS_WIN) | 35 #if defined (OS_WIN) |
| 31 FILE_PATH_LITERAL("human-voice-win.wav"); | 36 FILE_PATH_LITERAL("human-voice-win.wav"); |
| 32 #elif defined (OS_MACOSX) | 37 #elif defined (OS_MACOSX) |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 142 content::WebContents* tab_contents) { | 147 content::WebContents* tab_contents) { |
| 143 // This calls into webaudio.js. | 148 // This calls into webaudio.js. |
| 144 EXPECT_EQ("ok-added", ExecuteJavascript( | 149 EXPECT_EQ("ok-added", ExecuteJavascript( |
| 145 "addAudioFile('" + input_file_relative_url + "')", tab_contents)); | 150 "addAudioFile('" + input_file_relative_url + "')", tab_contents)); |
| 146 } | 151 } |
| 147 | 152 |
| 148 void PlayAudioFileThroughWebAudio(content::WebContents* tab_contents) { | 153 void PlayAudioFileThroughWebAudio(content::WebContents* tab_contents) { |
| 149 EXPECT_EQ("ok-playing", ExecuteJavascript("playAudioFile()", tab_contents)); | 154 EXPECT_EQ("ok-playing", ExecuteJavascript("playAudioFile()", tab_contents)); |
| 150 } | 155 } |
| 151 | 156 |
| 152 base::FilePath CreateTemporaryWaveFile() { | |
| 153 base::FilePath filename; | |
| 154 EXPECT_TRUE(base::CreateTemporaryFile(&filename)); | |
| 155 base::FilePath wav_filename = | |
| 156 filename.AddExtension(FILE_PATH_LITERAL(".wav")); | |
| 157 EXPECT_TRUE(base::Move(filename, wav_filename)); | |
| 158 return wav_filename; | |
| 159 } | |
| 160 | |
| 161 content::WebContents* OpenPageWithoutGetUserMedia(const char* url) { | 157 content::WebContents* OpenPageWithoutGetUserMedia(const char* url) { |
| 162 chrome::AddTabAt(browser(), GURL(), -1, true); | 158 chrome::AddTabAt(browser(), GURL(), -1, true); |
| 163 ui_test_utils::NavigateToURL( | 159 ui_test_utils::NavigateToURL( |
| 164 browser(), embedded_test_server()->GetURL(url)); | 160 browser(), embedded_test_server()->GetURL(url)); |
| 165 content::WebContents* tab = | 161 content::WebContents* tab = |
| 166 browser()->tab_strip_model()->GetActiveWebContents(); | 162 browser()->tab_strip_model()->GetActiveWebContents(); |
| 167 | 163 |
| 168 // Prepare the peer connections manually in this test since we don't add | 164 // Prepare the peer connections manually in this test since we don't add |
| 169 // getUserMedia-derived media streams in this test like the other tests. | 165 // getUserMedia-derived media streams in this test like the other tests. |
| 170 EXPECT_EQ("ok-peerconnection-created", | 166 EXPECT_EQ("ok-peerconnection-created", |
| 171 ExecuteJavascript("preparePeerConnection()", tab)); | 167 ExecuteJavascript("preparePeerConnection()", tab)); |
| 172 return tab; | 168 return tab; |
| 173 } | 169 } |
| 170 | |
| 171 protected: | |
| 172 void TestAutoGainControl(const base::FilePath::StringType& reference_filename, | |
| 173 const std::string& constraints, | |
| 174 const std::string& perf_modifier); | |
| 174 }; | 175 }; |
| 175 | 176 |
| 177 namespace { | |
| 178 | |
| 176 class AudioRecorder { | 179 class AudioRecorder { |
| 177 public: | 180 public: |
| 178 AudioRecorder() {} | 181 AudioRecorder() {} |
| 179 ~AudioRecorder() {} | 182 ~AudioRecorder() {} |
| 180 | 183 |
| 181 // Starts the recording program for the specified duration. Returns true | 184 // Starts the recording program for the specified duration. Returns true |
| 182 // on success. | 185 // on success. We record in CD format unless record_cd is false, in which case |
|
henrika (OOO until Aug 14)
2014/12/19 10:00:14
|record_cd| and please don't use CD/DAT format. Be
phoglund_chromium
2014/12/19 10:34:20
Done.
| |
| 186 // we record in DAT format. | |
| 187 // TODO(phoglund): make win and mac also support the record_cd parameter. Or, | |
| 188 // even better, make everybody use the CD format rather than DAT. | |
| 183 bool StartRecording(int duration_sec, const base::FilePath& output_file, | 189 bool StartRecording(int duration_sec, const base::FilePath& output_file, |
| 184 bool mono) { | 190 bool mono, bool record_cd) { |
| 185 EXPECT_FALSE(recording_application_.IsValid()) | 191 EXPECT_FALSE(recording_application_.IsValid()) |
| 186 << "Tried to record, but is already recording."; | 192 << "Tried to record, but is already recording."; |
| 187 | 193 |
| 188 CommandLine command_line(CommandLine::NO_PROGRAM); | 194 CommandLine command_line(CommandLine::NO_PROGRAM); |
| 189 #if defined(OS_WIN) | 195 #if defined(OS_WIN) |
| 190 // This disable is required to run SoundRecorder.exe on 64-bit Windows | 196 // This disable is required to run SoundRecorder.exe on 64-bit Windows |
| 191 // from a 32-bit binary. We need to load the wow64 disable function from | 197 // from a 32-bit binary. We need to load the wow64 disable function from |
| 192 // the DLL since it doesn't exist on Windows XP. | 198 // the DLL since it doesn't exist on Windows XP. |
| 193 // TODO(phoglund): find some cleaner solution than using SoundRecorder.exe. | 199 // TODO(phoglund): find some cleaner solution than using SoundRecorder.exe. |
| 194 base::ScopedNativeLibrary kernel32_lib(base::FilePath(L"kernel32")); | 200 base::ScopedNativeLibrary kernel32_lib(base::FilePath(L"kernel32")); |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 231 if (mono) { | 237 if (mono) { |
| 232 command_line.AppendArg("remix"); | 238 command_line.AppendArg("remix"); |
| 233 command_line.AppendArg("-"); | 239 command_line.AppendArg("-"); |
| 234 } | 240 } |
| 235 #else | 241 #else |
| 236 int num_channels = mono ? 1 : 2; | 242 int num_channels = mono ? 1 : 2; |
| 237 command_line.SetProgram(base::FilePath("arecord")); | 243 command_line.SetProgram(base::FilePath("arecord")); |
| 238 command_line.AppendArg("-d"); | 244 command_line.AppendArg("-d"); |
| 239 command_line.AppendArg(base::StringPrintf("%d", duration_sec)); | 245 command_line.AppendArg(base::StringPrintf("%d", duration_sec)); |
| 240 command_line.AppendArg("-f"); | 246 command_line.AppendArg("-f"); |
| 241 command_line.AppendArg("dat"); | 247 if (record_cd) |
| 248 command_line.AppendArg("cd"); | |
| 249 else | |
| 250 command_line.AppendArg("dat"); | |
| 242 command_line.AppendArg("-c"); | 251 command_line.AppendArg("-c"); |
| 243 command_line.AppendArg(base::StringPrintf("%d", num_channels)); | 252 command_line.AppendArg(base::StringPrintf("%d", num_channels)); |
| 244 command_line.AppendArgPath(output_file); | 253 command_line.AppendArgPath(output_file); |
| 245 #endif | 254 #endif |
| 246 | 255 |
| 247 DVLOG(0) << "Running " << command_line.GetCommandLineString(); | 256 DVLOG(0) << "Running " << command_line.GetCommandLineString(); |
| 248 recording_application_ = | 257 recording_application_ = |
| 249 base::LaunchProcess(command_line, base::LaunchOptions()); | 258 base::LaunchProcess(command_line, base::LaunchOptions()); |
| 250 return recording_application_.IsValid(); | 259 return recording_application_.IsValid(); |
| 251 } | 260 } |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 297 DVLOG(0) << "Running " << command_line.GetCommandLineString(); | 306 DVLOG(0) << "Running " << command_line.GetCommandLineString(); |
| 298 if (!base::GetAppOutput(command_line, &result)) { | 307 if (!base::GetAppOutput(command_line, &result)) { |
| 299 LOG(ERROR) << "Failed to set source volume: output was " << result; | 308 LOG(ERROR) << "Failed to set source volume: output was " << result; |
| 300 return false; | 309 return false; |
| 301 } | 310 } |
| 302 } | 311 } |
| 303 #endif | 312 #endif |
| 304 return true; | 313 return true; |
| 305 } | 314 } |
| 306 | 315 |
| 316 CommandLine MakeSoxCommandLine() { | |
|
henrika (OOO until Aug 14)
2014/12/19 10:00:14
Could you add some comments and reference to SoX?
phoglund_chromium
2014/12/19 10:34:20
Done.
| |
| 317 #if defined(OS_WIN) | |
| 318 base::FilePath sox_path = test::GetReferenceFilesDir().Append( | |
| 319 FILE_PATH_LITERAL("tools/sox.exe")); | |
| 320 if (!base::PathExists(sox_path)) { | |
| 321 LOG(ERROR) << "Missing sox.exe binary in " << sox_path.value() | |
| 322 << "; you may have to provide this binary yourself."; | |
| 323 return false; | |
| 324 } | |
| 325 CommandLine command_line(sox_path); | |
| 326 #else | |
| 327 CommandLine command_line(base::FilePath(FILE_PATH_LITERAL("sox"))); | |
| 328 #endif | |
| 329 return command_line; | |
| 330 } | |
| 331 | |
| 307 // Removes silence from beginning and end of the |input_audio_file| and writes | 332 // Removes silence from beginning and end of the |input_audio_file| and writes |
| 308 // the result to the |output_audio_file|. Returns true on success. | 333 // the result to the |output_audio_file|. Returns true on success. |
| 309 bool RemoveSilence(const base::FilePath& input_file, | 334 bool RemoveSilence(const base::FilePath& input_file, |
| 310 const base::FilePath& output_file) { | 335 const base::FilePath& output_file) { |
| 311 // SOX documentation for silence command: http://sox.sourceforge.net/sox.html | 336 // SOX documentation for silence command: http://sox.sourceforge.net/sox.html |
| 312 // To remove the silence from both beginning and end of the audio file, we | 337 // To remove the silence from both beginning and end of the audio file, we |
| 313 // call sox silence command twice: once on normal file and again on its | 338 // call sox silence command twice: once on normal file and again on its |
| 314 // reverse, then we reverse the final output. | 339 // reverse, then we reverse the final output. |
| 315 // Silence parameters are (in sequence): | 340 // Silence parameters are (in sequence): |
| 316 // ABOVE_PERIODS: The period for which silence occurs. Value 1 is used for | 341 // ABOVE_PERIODS: The period for which silence occurs. Value 1 is used for |
| 317 // silence at beginning of audio. | 342 // silence at beginning of audio. |
| 318 // DURATION: the amount of time in seconds that non-silence must be detected | 343 // DURATION: the amount of time in seconds that non-silence must be detected |
| 319 // before sox stops trimming audio. | 344 // before sox stops trimming audio. |
| 320 // THRESHOLD: value used to indicate what sample value is treates as silence. | 345 // THRESHOLD: value used to indicate what sample value is treats as silence. |
| 321 const char* kAbovePeriods = "1"; | 346 const char* kAbovePeriods = "1"; |
|
henrika (OOO until Aug 14)
2014/12/19 10:00:14
How essential are these exact parameter values for
phoglund_chromium
2014/12/19 10:34:20
It depends a lot on the files we feed in. With the
| |
| 322 const char* kDuration = "2"; | 347 const char* kDuration = "2"; |
| 323 const char* kTreshold = "5%"; | 348 const char* kTreshold = "3%"; |
| 324 | 349 |
| 325 #if defined(OS_WIN) | 350 CommandLine command_line = MakeSoxCommandLine(); |
| 326 base::FilePath sox_path = test::GetReferenceFilesDir().Append( | |
| 327 FILE_PATH_LITERAL("tools/sox.exe")); | |
| 328 if (!base::PathExists(sox_path)) { | |
| 329 LOG(ERROR) << "Missing sox.exe binary in " << sox_path.value() | |
| 330 << "; you may have to provide this binary yourself."; | |
| 331 return false; | |
| 332 } | |
| 333 CommandLine command_line(sox_path); | |
| 334 #else | |
| 335 CommandLine command_line(base::FilePath(FILE_PATH_LITERAL("sox"))); | |
| 336 #endif | |
| 337 command_line.AppendArgPath(input_file); | 351 command_line.AppendArgPath(input_file); |
| 338 command_line.AppendArgPath(output_file); | 352 command_line.AppendArgPath(output_file); |
| 339 command_line.AppendArg("silence"); | 353 command_line.AppendArg("silence"); |
| 340 command_line.AppendArg(kAbovePeriods); | 354 command_line.AppendArg(kAbovePeriods); |
| 341 command_line.AppendArg(kDuration); | 355 command_line.AppendArg(kDuration); |
| 342 command_line.AppendArg(kTreshold); | 356 command_line.AppendArg(kTreshold); |
| 343 command_line.AppendArg("reverse"); | 357 command_line.AppendArg("reverse"); |
| 344 command_line.AppendArg("silence"); | 358 command_line.AppendArg("silence"); |
| 345 command_line.AppendArg(kAbovePeriods); | 359 command_line.AppendArg(kAbovePeriods); |
| 346 command_line.AppendArg(kDuration); | 360 command_line.AppendArg(kDuration); |
| 347 command_line.AppendArg(kTreshold); | 361 command_line.AppendArg(kTreshold); |
| 348 command_line.AppendArg("reverse"); | 362 command_line.AppendArg("reverse"); |
| 349 | 363 |
| 350 DVLOG(0) << "Running " << command_line.GetCommandLineString(); | 364 DVLOG(0) << "Running " << command_line.GetCommandLineString(); |
| 351 std::string result; | 365 std::string result; |
| 352 bool ok = base::GetAppOutput(command_line, &result); | 366 bool ok = base::GetAppOutput(command_line, &result); |
| 353 DVLOG(0) << "Output was:\n\n" << result; | 367 DVLOG(0) << "Output was:\n\n" << result; |
| 354 return ok; | 368 return ok; |
| 355 } | 369 } |
| 356 | 370 |
| 371 // Looks for 0.3-second silences (under 1% audio power) and splits the input | |
| 372 // file on those silences. Output files are written according to the output file | |
| 373 // template (e.g. /tmp/out.wav writes /tmp/out001.wav, /tmp/out002.wav, etc if | |
| 374 // there are two silence-padded regions in the file). The silences between | |
| 375 // speech segments must be at least 500 ms for this to be reliable. | |
| 376 bool SplitFileOnSilence(const base::FilePath& input_file, | |
| 377 const base::FilePath& output_file_template) { | |
| 378 CommandLine command_line = MakeSoxCommandLine(); | |
| 379 | |
| 380 // These are experimentally determined and work on the files we use. | |
| 381 const char* kAbovePeriods = "1"; | |
| 382 const char* kUnderPeriods = "1"; | |
| 383 const char* kDuration = "0.3"; | |
| 384 const char* kTreshold = "1%"; | |
| 385 command_line.AppendArgPath(input_file); | |
| 386 command_line.AppendArgPath(output_file_template); | |
| 387 command_line.AppendArg("silence"); | |
| 388 command_line.AppendArg(kAbovePeriods); | |
| 389 command_line.AppendArg(kDuration); | |
| 390 command_line.AppendArg(kTreshold); | |
| 391 command_line.AppendArg(kUnderPeriods); | |
| 392 command_line.AppendArg(kDuration); | |
| 393 command_line.AppendArg(kTreshold); | |
| 394 command_line.AppendArg(":"); | |
| 395 command_line.AppendArg("newfile"); | |
| 396 command_line.AppendArg(":"); | |
| 397 command_line.AppendArg("restart"); | |
| 398 | |
| 399 DVLOG(0) << "Running " << command_line.GetCommandLineString(); | |
| 400 std::string result; | |
| 401 bool ok = base::GetAppOutput(command_line, &result); | |
| 402 DVLOG(0) << "Output was:\n\n" << result; | |
| 403 return ok; | |
| 404 } | |
| 405 | |
| 357 bool CanParseAsFloat(const std::string& value) { | 406 bool CanParseAsFloat(const std::string& value) { |
| 358 return atof(value.c_str()) != 0 || value == "0"; | 407 return atof(value.c_str()) != 0 || value == "0"; |
| 359 } | 408 } |
| 360 | 409 |
| 361 // Runs PESQ to compare |reference_file| to a |actual_file|. The |sample_rate| | 410 // Runs PESQ to compare |reference_file| to a |actual_file|. The |sample_rate| |
| 362 // can be either 16000 or 8000. | 411 // can be either 16000 or 8000. |
| 363 // | 412 // |
| 364 // PESQ is only mono-aware, so the files should preferably be recorded in mono. | 413 // PESQ is only mono-aware, so the files should preferably be recorded in mono. |
| 365 // Furthermore it expects the file to be 16 rather than 32 bits, even though | 414 // Furthermore it expects the file to be 16 rather than 32 bits, even though |
| 366 // 32 bits might work. The audio bandwidth of the two files should be the same | 415 // 32 bits might work. The audio bandwidth of the two files should be the same |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 417 // There are two tab-separated numbers on the format x.xxx, e.g. 5 chars each. | 466 // There are two tab-separated numbers on the format x.xxx, e.g. 5 chars each. |
| 418 std::size_t first_number_pos = anchor_pos + result_anchor.length(); | 467 std::size_t first_number_pos = anchor_pos + result_anchor.length(); |
| 419 *raw_mos = result.substr(first_number_pos, 5); | 468 *raw_mos = result.substr(first_number_pos, 5); |
| 420 EXPECT_TRUE(CanParseAsFloat(*raw_mos)) << "Failed to parse raw MOS number."; | 469 EXPECT_TRUE(CanParseAsFloat(*raw_mos)) << "Failed to parse raw MOS number."; |
| 421 *mos_lqo = result.substr(first_number_pos + 5 + 1, 5); | 470 *mos_lqo = result.substr(first_number_pos + 5 + 1, 5); |
| 422 EXPECT_TRUE(CanParseAsFloat(*mos_lqo)) << "Failed to parse MOS LQO number."; | 471 EXPECT_TRUE(CanParseAsFloat(*mos_lqo)) << "Failed to parse MOS LQO number."; |
| 423 | 472 |
| 424 return true; | 473 return true; |
| 425 } | 474 } |
| 426 | 475 |
| 476 base::FilePath CreateTemporaryWaveFile() { | |
| 477 base::FilePath filename; | |
| 478 EXPECT_TRUE(base::CreateTemporaryFile(&filename)); | |
| 479 base::FilePath wav_filename = | |
| 480 filename.AddExtension(FILE_PATH_LITERAL(".wav")); | |
| 481 EXPECT_TRUE(base::Move(filename, wav_filename)); | |
| 482 return wav_filename; | |
| 483 } | |
| 484 | |
| 485 std::vector<base::FilePath> ListWavFilesInDir(const base::FilePath& dir) { | |
| 486 base::FileEnumerator files(dir, false, base::FileEnumerator::FILES, | |
| 487 FILE_PATH_LITERAL("*.wav")); | |
| 488 | |
| 489 std::vector<base::FilePath> result; | |
| 490 for (base::FilePath name = files.Next(); !name.empty(); name = files.Next()) | |
| 491 result.push_back(name); | |
| 492 return result; | |
| 493 } | |
| 494 | |
| 495 // Splits |to_split| into sub-files base on silence. The file you use must have | |
|
henrika (OOO until Aug 14)
2014/12/19 10:00:14
nit, 'based on'
phoglund_chromium
2014/12/19 10:34:20
Done.
| |
| 496 // at least 500 ms periods of silence between speech segments for this to be | |
| 497 // reliable. | |
| 498 void SplitFileOnSilenceIntoDir(const base::FilePath& to_split, | |
| 499 const base::FilePath& workdir) { | |
| 500 // First trim beginning and end since they are tricky for the splitter. | |
| 501 base::FilePath trimmed_audio = CreateTemporaryWaveFile(); | |
| 502 | |
| 503 ASSERT_TRUE(RemoveSilence(to_split, trimmed_audio)); | |
| 504 DVLOG(0) << "Trimmed silence: " << trimmed_audio.value() << std::endl; | |
| 505 | |
| 506 ASSERT_TRUE(SplitFileOnSilence(trimmed_audio, workdir.Append("output.wav"))); | |
| 507 ASSERT_TRUE(base::DeleteFile(trimmed_audio, false)); | |
| 508 } | |
| 509 | |
| 510 // Computes the difference between the actual and reference segment. A positive | |
| 511 // number x means the actual file is x dB stronger than the reference. | |
| 512 float AnalyzeOneSegment(const base::FilePath& ref_segment, | |
| 513 const base::FilePath& actual_segment, | |
| 514 int segment_number) { | |
| 515 media::AudioParameters ref_parameters; | |
| 516 media::AudioParameters actual_parameters; | |
| 517 float ref_energy = | |
| 518 test::ComputeAudioEnergyForWavFile(ref_segment, &ref_parameters); | |
| 519 float actual_energy = | |
| 520 test::ComputeAudioEnergyForWavFile(actual_segment, &actual_parameters); | |
| 521 | |
| 522 base::TimeDelta difference_in_length = ref_parameters.GetBufferDuration() - | |
| 523 actual_parameters.GetBufferDuration(); | |
| 524 EXPECT_LE(difference_in_length, base::TimeDelta::FromMilliseconds(200)) | |
| 525 << "Segments differ " << difference_in_length.InMilliseconds() << " ms " | |
| 526 << "in length for segment " << segment_number << "; we're likely " | |
| 527 << "comparing unrelated segments or silence splitting is busted."; | |
| 528 | |
| 529 return actual_energy - ref_energy; | |
| 530 } | |
| 531 | |
| 532 void AnalyzeSegmentsAndPrintResult( | |
| 533 const std::vector<base::FilePath>& ref_segments, | |
| 534 const std::vector<base::FilePath>& actual_segments, | |
| 535 const base::FilePath& reference_file, | |
| 536 const std::string& perf_modifier) { | |
| 537 ASSERT_GT(ref_segments.size(), 0u) | |
| 538 << "Failed to split reference file on silence; sox is likely broken."; | |
| 539 ASSERT_EQ(ref_segments.size(), actual_segments.size()) | |
| 540 << "The recording did not result in the same number of audio segments " | |
| 541 << "after on splitting on silence; WebRTC must have deformed the audio " | |
| 542 << "too much."; | |
| 543 | |
| 544 for (size_t i = 0; i < ref_segments.size(); i++) { | |
| 545 float difference_in_decibel = AnalyzeOneSegment(ref_segments[i], | |
| 546 actual_segments[i], | |
| 547 i); | |
| 548 std::string trace_name = base::StringPrintf( | |
| 549 "%s_segment_%zu", reference_file.BaseName().value().c_str(), i); | |
| 550 perf_test::PrintResult("agc_energy_diff", perf_modifier, trace_name, | |
| 551 difference_in_decibel, "dB", false); | |
| 552 } | |
| 553 } | |
| 554 | |
| 555 } // namespace | |
| 556 | |
| 427 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest, | 557 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest, |
| 428 MANUAL_TestAudioQuality) { | 558 MANUAL_TestAudioQuality) { |
| 429 if (OnWinXp()) { | 559 if (OnWinXp()) { |
| 430 LOG(ERROR) << "This test is not implemented for Windows XP."; | 560 LOG(ERROR) << "This test is not implemented for Windows XP."; |
| 431 return; | 561 return; |
| 432 } | 562 } |
| 433 if (OnWin8()) { | 563 if (OnWin8()) { |
| 434 // http://crbug.com/379798. | 564 // http://crbug.com/379798. |
| 435 LOG(ERROR) << "Temporarily disabled for Win 8."; | 565 LOG(ERROR) << "Temporarily disabled for Win 8."; |
| 436 return; | 566 return; |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 453 // because the ready state is ok on both sides. We sleep a bit between call | 583 // because the ready state is ok on both sides. We sleep a bit between call |
| 454 // establishment and playing to avoid cutting off the beginning of the stream. | 584 // establishment and playing to avoid cutting off the beginning of the stream. |
| 455 test::SleepInJavascript(left_tab, 2000); | 585 test::SleepInJavascript(left_tab, 2000); |
| 456 | 586 |
| 457 base::FilePath recording = CreateTemporaryWaveFile(); | 587 base::FilePath recording = CreateTemporaryWaveFile(); |
| 458 | 588 |
| 459 // Note: the sound clip is about 10 seconds: record for 15 seconds to get some | 589 // Note: the sound clip is about 10 seconds: record for 15 seconds to get some |
| 460 // safety margins on each side. | 590 // safety margins on each side. |
| 461 AudioRecorder recorder; | 591 AudioRecorder recorder; |
| 462 static int kRecordingTimeSeconds = 15; | 592 static int kRecordingTimeSeconds = 15; |
| 463 ASSERT_TRUE(recorder.StartRecording(kRecordingTimeSeconds, recording, true)); | 593 ASSERT_TRUE(recorder.StartRecording(kRecordingTimeSeconds, recording, |
| 594 true, false)); | |
| 464 | 595 |
| 465 PlayAudioFileThroughWebAudio(left_tab); | 596 PlayAudioFileThroughWebAudio(left_tab); |
| 466 | 597 |
| 467 ASSERT_TRUE(recorder.WaitForRecordingToEnd()); | 598 ASSERT_TRUE(recorder.WaitForRecordingToEnd()); |
| 468 DVLOG(0) << "Done recording to " << recording.value() << std::endl; | 599 DVLOG(0) << "Done recording to " << recording.value() << std::endl; |
| 469 | 600 |
| 470 HangUp(left_tab); | 601 HangUp(left_tab); |
| 471 | 602 |
| 472 base::FilePath trimmed_recording = CreateTemporaryWaveFile(); | 603 base::FilePath trimmed_recording = CreateTemporaryWaveFile(); |
| 473 | 604 |
| 474 ASSERT_TRUE(RemoveSilence(recording, trimmed_recording)); | 605 ASSERT_TRUE(RemoveSilence(recording, trimmed_recording)); |
| 475 DVLOG(0) << "Trimmed silence: " << trimmed_recording.value() << std::endl; | 606 DVLOG(0) << "Trimmed silence: " << trimmed_recording.value() << std::endl; |
| 476 | 607 |
| 477 std::string raw_mos; | 608 std::string raw_mos; |
| 478 std::string mos_lqo; | 609 std::string mos_lqo; |
| 479 base::FilePath reference_file_in_test_dir = | 610 base::FilePath reference_file_in_test_dir = |
| 480 test::GetReferenceFilesDir().Append(kReferenceFile); | 611 test::GetReferenceFilesDir().Append(kReferenceFile); |
| 481 ASSERT_TRUE(RunPesq(reference_file_in_test_dir, trimmed_recording, 16000, | 612 ASSERT_TRUE(RunPesq(reference_file_in_test_dir, trimmed_recording, 16000, |
| 482 &raw_mos, &mos_lqo)); | 613 &raw_mos, &mos_lqo)); |
| 483 | 614 |
| 484 perf_test::PrintResult("audio_pesq", "", "raw_mos", raw_mos, "score", true); | 615 perf_test::PrintResult("audio_pesq", "", "raw_mos", raw_mos, "score", true); |
| 485 perf_test::PrintResult("audio_pesq", "", "mos_lqo", mos_lqo, "score", true); | 616 perf_test::PrintResult("audio_pesq", "", "mos_lqo", mos_lqo, "score", true); |
| 486 | 617 |
| 487 EXPECT_TRUE(base::DeleteFile(recording, false)); | 618 EXPECT_TRUE(base::DeleteFile(recording, false)); |
| 488 EXPECT_TRUE(base::DeleteFile(trimmed_recording, false)); | 619 EXPECT_TRUE(base::DeleteFile(trimmed_recording, false)); |
| 489 } | 620 } |
| 490 | 621 |
| 491 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest, | 622 /** |
| 492 MANUAL_TestAutoGainControlIncreasesEnergyForLowAudio) { | 623 * The auto gain control test plays a file into the fake microphone. Then it |
| 624 * sets up a one-way WebRTC call with audio only and records Chrome's output on | |
| 625 * the receiving side using the audio loopback provided by the quality test | |
| 626 * (see the class comments for more details). | |
| 627 * | |
| 628 * Then both the recording and reference file are split on silence. This creates | |
| 629 * a number of segments with speech in them. The reason for this is to provide | |
| 630 * a kind of synchronization mechanism so the start of each speech segment is | |
| 631 * compared to the start of the corresponding speech segment. This is because we | |
| 632 * will experience inevitable clock drift between the system clock (which runs | |
| 633 * the fake microphone) and the sound card (which runs play-out). Effectively | |
| 634 * re-synchronizing on each segment mitigates this. | |
| 635 * | |
| 636 * The silence splitting is inherently sensitive to the sound file we run on. | |
| 637 * Therefore the reference file must have at least 500 ms of pure silence | |
| 638 * between speech segments; the test will fail if the output produces more | |
| 639 * segments than the reference. | |
| 640 * | |
| 641 * The test reports the difference in decibel between the reference and output | |
| 642 * file per 10 ms interval in each speech segment. A value of 6 means the | |
| 643 * output was 6 dB louder than the reference, presumably because the AGC applied | |
| 644 * gain to the signal. | |
| 645 * | |
| 646 * The test only exercises digital AGC for now. | |
| 647 * | |
| 648 * We record in CD format here (44.1 kHz) because that's what the fake input | |
| 649 * device currently supports, and we want to be able to compare directly. | |
|
henrika (OOO until Aug 14)
2014/12/19 10:00:14
Any link to a crbug here?
phoglund_chromium
2014/12/19 10:34:20
Sure, I can link to the fake device bug where the
| |
| 650 */ | |
| 651 void MAYBE_WebRtcAudioQualityBrowserTest::TestAutoGainControl( | |
| 652 const base::FilePath::StringType& reference_filename, | |
| 653 const std::string& constraints, | |
| 654 const std::string& perf_modifier) { | |
| 493 if (OnWinXp()) { | 655 if (OnWinXp()) { |
| 494 LOG(ERROR) << "This test is not implemented for Windows XP."; | 656 LOG(ERROR) << "This test is not implemented for Windows XP."; |
| 495 return; | 657 return; |
| 496 } | 658 } |
| 497 if (OnWin8()) { | 659 if (OnWin8()) { |
| 498 // http://crbug.com/379798. | 660 // http://crbug.com/379798. |
| 499 LOG(ERROR) << "Temporarily disabled for Win 8."; | 661 LOG(ERROR) << "Temporarily disabled for Win 8."; |
| 500 return; | 662 return; |
| 501 } | 663 } |
| 502 ASSERT_TRUE(test::HasReferenceFilesInCheckout()); | 664 ASSERT_TRUE(test::HasReferenceFilesInCheckout()); |
| 503 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); | 665 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); |
| 504 | 666 |
| 505 ASSERT_TRUE(ForceMicrophoneVolumeTo100Percent()); | 667 ASSERT_TRUE(ForceMicrophoneVolumeTo100Percent()); |
| 506 | 668 |
| 507 ConfigureFakeDeviceToPlayFile( | 669 base::FilePath reference_file = |
| 508 test::GetReferenceFilesDir().Append(kAgcTestReferenceFile)); | 670 test::GetReferenceFilesDir().Append(reference_filename); |
| 671 ConfigureFakeDeviceToPlayFile(reference_file); | |
| 509 | 672 |
| 510 // Create a one-way call. | 673 // Create a one-way call. |
| 511 GURL test_page = embedded_test_server()->GetURL(kWebRtcAudioTestHtmlPage); | 674 GURL test_page = embedded_test_server()->GetURL(kWebRtcAudioTestHtmlPage); |
| 512 content::WebContents* left_tab = | 675 content::WebContents* left_tab = |
| 513 OpenPageAndGetUserMediaInNewTabWithConstraints(test_page, | 676 OpenPageAndGetUserMediaInNewTabWithConstraints(test_page, constraints); |
| 514 kAudioOnlyCallConstraints); | |
| 515 SetupPeerconnectionWithLocalStream(left_tab); | 677 SetupPeerconnectionWithLocalStream(left_tab); |
| 516 | 678 |
| 517 content::WebContents* right_tab = | 679 content::WebContents* right_tab = |
| 518 OpenPageWithoutGetUserMedia(kWebRtcAudioTestHtmlPage); | 680 OpenPageWithoutGetUserMedia(kWebRtcAudioTestHtmlPage); |
| 519 | 681 |
| 520 NegotiateCall(left_tab, right_tab); | |
| 521 | |
| 522 base::FilePath recording = CreateTemporaryWaveFile(); | 682 base::FilePath recording = CreateTemporaryWaveFile(); |
| 523 | 683 |
| 524 AudioRecorder recorder; | 684 AudioRecorder recorder; |
| 525 static int kRecordingTimeSeconds = 10; | 685 static int kRecordingTimeSeconds = 25; |
| 526 ASSERT_TRUE(recorder.StartRecording(kRecordingTimeSeconds, recording, true)); | 686 ASSERT_TRUE(recorder.StartRecording(kRecordingTimeSeconds, recording, false, |
| 687 true)); | |
| 688 | |
| 689 NegotiateCall(left_tab, right_tab); | |
| 527 | 690 |
| 528 ASSERT_TRUE(recorder.WaitForRecordingToEnd()); | 691 ASSERT_TRUE(recorder.WaitForRecordingToEnd()); |
| 529 DVLOG(0) << "Done recording to " << recording.value() << std::endl; | 692 DVLOG(0) << "Done recording to " << recording.value() << std::endl; |
| 530 | 693 |
| 531 HangUp(left_tab); | 694 HangUp(left_tab); |
| 532 | 695 |
| 533 base::FilePath trimmed_recording = CreateTemporaryWaveFile(); | 696 // Call Take() on the scoped temp dirs if you want to look at the files after |
| 697 // the test exits (the default is to delete the files). | |
| 698 base::ScopedTempDir split_ref_files; | |
| 699 ASSERT_TRUE(split_ref_files.CreateUniqueTempDir()); | |
| 700 ASSERT_NO_FATAL_FAILURE( | |
| 701 SplitFileOnSilenceIntoDir(reference_file, split_ref_files.path())); | |
| 702 std::vector<base::FilePath> ref_segments = | |
| 703 ListWavFilesInDir(split_ref_files.path()); | |
| 534 | 704 |
| 535 ASSERT_TRUE(RemoveSilence(recording, trimmed_recording)); | 705 base::ScopedTempDir split_actual_files; |
| 536 DVLOG(0) << "Trimmed silence: " << trimmed_recording.value() << std::endl; | 706 ASSERT_TRUE(split_actual_files.CreateUniqueTempDir()); |
| 707 ASSERT_NO_FATAL_FAILURE( | |
| 708 SplitFileOnSilenceIntoDir(recording, split_actual_files.path())); | |
| 709 std::vector<base::FilePath> actual_segments = | |
| 710 ListWavFilesInDir(split_actual_files.path()); | |
| 537 | 711 |
| 538 // TODO(phoglund): invoke bjornv's audio energy analysis tool on the trimmed | 712 AnalyzeSegmentsAndPrintResult(ref_segments, actual_segments, reference_file, |
| 539 // recording and log the result. | 713 perf_modifier); |
| 540 | 714 |
| 541 EXPECT_TRUE(base::DeleteFile(recording, false)); | 715 EXPECT_TRUE(base::DeleteFile(recording, false)); |
| 542 EXPECT_TRUE(base::DeleteFile(trimmed_recording, false)); | |
| 543 } | 716 } |
| 717 | |
| 718 // Only implemented for Linux for now. | |
| 719 #if defined(OS_LINUX) | |
| 720 #define MAYBE_MANUAL_TestAutoGainControlOnLowAudio \ | |
| 721 MANUAL_TestAutoGainControlOnLowAudio | |
| 722 #else | |
| 723 #define MAYBE_MANUAL_TestAutoGainControlOnLowAudio \ | |
| 724 DISABLED_MANUAL_TestAutoGainControlOnLowAudio | |
| 725 #endif | |
| 726 | |
| 727 // The AGC should apply gain here on the order of 5-6 dBFS units. | |
|
henrika (OOO until Aug 14)
2014/12/19 10:00:14
How do you know this gain? is it digital only?
phoglund_chromium
2014/12/19 10:34:20
I guess I don't really, but those are the results
| |
| 728 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest, | |
| 729 MAYBE_MANUAL_TestAutoGainControlOnLowAudio) { | |
| 730 ASSERT_NO_FATAL_FAILURE(TestAutoGainControl( | |
| 731 kAgcTestReferenceFile, kAudioOnlyCallConstraints, "_with_agc")); | |
| 732 } | |
| 733 | |
| 734 // Only implemented for Linux for now. | |
| 735 #if defined(OS_LINUX) | |
| 736 #define MAYBE_MANUAL_TestComputeGainWithAudioProcessingOff \ | |
| 737 MANUAL_TestComputeGainWithAudioProcessingOff | |
| 738 #else | |
| 739 #define MAYBE_MANUAL_TestComputeGainWithAudioProcessingOff \ | |
| 740 DISABLED_MANUAL_TestComputeGainWithAudioProcessingOff | |
| 741 #endif | |
| 742 | |
| 743 // Since the AGC is off here there should be no gain at all. | |
| 744 IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcAudioQualityBrowserTest, | |
| 745 MAYBE_MANUAL_TestComputeGainWithAudioProcessingOff) { | |
| 746 const char* kAudioCallWithoutAudioProcessing = | |
| 747 "{audio: { mandatory: { echoCancellation: false } } }"; | |
| 748 ASSERT_NO_FATAL_FAILURE(TestAutoGainControl( | |
| 749 kAgcTestReferenceFile, kAudioCallWithoutAudioProcessing, "_no_agc")); | |
| 750 } | |
| 751 | |
| OLD | NEW |