Low-bandwidth audio testing
The C++ part of the test uses CallTest to set up an audio-only call. It reads an audio file, plays it through a FakeAudioDevice which transfers data through a FakeNetworkPipe for another FakeAudioDevice to receive it and write it to a file. Information about these files is printed to stdout. The test cases are meant to try different network and audio configs (more are planned in the future). The Python part of the test runs the C++ part and scans stdout for tests to perform, runs the pairs of files (original and degraded) through the PESQ tool to receive a score and writes that to perf dashboard. BUG=webrtc:7229 NOTRY=True Review-Url: https://codereview.webrtc.org/2694203002 Cr-Commit-Position: refs/heads/master@{#17356}
This commit is contained in:
parent
2aa463f721
commit
92220ffe9f
@ -94,12 +94,28 @@ if (rtc_include_tests) {
|
||||
|
||||
sources = [
|
||||
"test/low_bandwidth_audio_test.cc",
|
||||
"test/low_bandwidth_audio_test.h",
|
||||
]
|
||||
|
||||
deps = []
|
||||
|
||||
deps = [
|
||||
"../common_audio",
|
||||
"../system_wrappers",
|
||||
"../test:fake_audio_device",
|
||||
"../test:run_test",
|
||||
"../test:test_common",
|
||||
"../test:test_main",
|
||||
]
|
||||
if (is_android) {
|
||||
deps += [ "//testing/android/native_test:native_test_support" ]
|
||||
deps += [ "//testing/android/native_test:native_test_native_code" ]
|
||||
}
|
||||
|
||||
data = [
|
||||
"//resources/voice_engine/audio_tiny16.wav",
|
||||
]
|
||||
|
||||
if (!build_with_chromium && is_clang) {
|
||||
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163)
|
||||
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
include_rules = [
|
||||
"+webrtc/base",
|
||||
"+webrtc/call",
|
||||
"+webrtc/common_audio/resampler",
|
||||
"+webrtc/common_audio",
|
||||
"+webrtc/logging/rtc_event_log",
|
||||
"+webrtc/modules/audio_coding/codecs/mock",
|
||||
"+webrtc/modules/audio_coding",
|
||||
"+webrtc/modules/audio_device",
|
||||
"+webrtc/modules/audio_mixer",
|
||||
"+webrtc/modules/audio_processing/include",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 The WebRTC Project Authors. All rights reserved.
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
@ -8,9 +8,144 @@
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
// This is a placeholder for the work oprypin@ is doing on a low-bandwidth
|
||||
// audio test executable.
|
||||
#include <algorithm>
|
||||
|
||||
int main() {
|
||||
#include "webrtc/audio/test/low_bandwidth_audio_test.h"
|
||||
#include "webrtc/common_audio/wav_file.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/test/run_test.h"
|
||||
#include "webrtc/system_wrappers/include/sleep.h"
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
|
||||
namespace {
|
||||
// Wait half a second between stopping sending and stopping receiving audio.
|
||||
constexpr int kExtraRecordTimeMs = 500;
|
||||
|
||||
// Large bitrate by default.
|
||||
const webrtc::CodecInst kDefaultCodec{120, "OPUS", 48000, 960, 2, 64000};
|
||||
|
||||
// The best that can be done with PESQ.
|
||||
constexpr int kAudioFileBitRate = 16000;
|
||||
}
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
AudioQualityTest::AudioQualityTest()
|
||||
: EndToEndTest(CallTest::kDefaultTimeoutMs) {}
|
||||
|
||||
size_t AudioQualityTest::GetNumVideoStreams() const {
|
||||
return 0;
|
||||
}
|
||||
size_t AudioQualityTest::GetNumAudioStreams() const {
|
||||
return 1;
|
||||
}
|
||||
size_t AudioQualityTest::GetNumFlexfecStreams() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string AudioQualityTest::AudioInputFile() {
|
||||
return test::ResourcePath("voice_engine/audio_tiny16", "wav");
|
||||
}
|
||||
|
||||
std::string AudioQualityTest::AudioOutputFile() {
|
||||
const ::testing::TestInfo* const test_info =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
return webrtc::test::OutputPath() +
|
||||
"LowBandwidth_" + test_info->name() + ".wav";
|
||||
}
|
||||
|
||||
std::unique_ptr<test::FakeAudioDevice::Capturer>
|
||||
AudioQualityTest::CreateCapturer() {
|
||||
return test::FakeAudioDevice::CreateWavFileReader(AudioInputFile());
|
||||
}
|
||||
|
||||
std::unique_ptr<test::FakeAudioDevice::Renderer>
|
||||
AudioQualityTest::CreateRenderer() {
|
||||
return test::FakeAudioDevice::CreateBoundedWavFileWriter(
|
||||
AudioOutputFile(), kAudioFileBitRate);
|
||||
}
|
||||
|
||||
void AudioQualityTest::OnFakeAudioDevicesCreated(
|
||||
test::FakeAudioDevice* send_audio_device,
|
||||
test::FakeAudioDevice* recv_audio_device) {
|
||||
send_audio_device_ = send_audio_device;
|
||||
}
|
||||
|
||||
FakeNetworkPipe::Config AudioQualityTest::GetNetworkPipeConfig() {
|
||||
return FakeNetworkPipe::Config();
|
||||
}
|
||||
|
||||
test::PacketTransport* AudioQualityTest::CreateSendTransport(
|
||||
Call* sender_call) {
|
||||
return new test::PacketTransport(
|
||||
sender_call, this, test::PacketTransport::kSender,
|
||||
GetNetworkPipeConfig());
|
||||
}
|
||||
|
||||
test::PacketTransport* AudioQualityTest::CreateReceiveTransport() {
|
||||
return new test::PacketTransport(nullptr, this,
|
||||
test::PacketTransport::kReceiver, GetNetworkPipeConfig());
|
||||
}
|
||||
|
||||
void AudioQualityTest::ModifyAudioConfigs(
|
||||
AudioSendStream::Config* send_config,
|
||||
std::vector<AudioReceiveStream::Config>* receive_configs) {
|
||||
send_config->send_codec_spec.codec_inst = kDefaultCodec;
|
||||
}
|
||||
|
||||
void AudioQualityTest::PerformTest() {
|
||||
// Wait until the input audio file is done...
|
||||
send_audio_device_->WaitForRecordingEnd();
|
||||
// and some extra time to account for network delay.
|
||||
SleepMs(GetNetworkPipeConfig().queue_delay_ms + kExtraRecordTimeMs);
|
||||
}
|
||||
|
||||
void AudioQualityTest::OnTestFinished() {
|
||||
const ::testing::TestInfo* const test_info =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
|
||||
// Output information about the input and output audio files so that further
|
||||
// processing can be done by an external process.
|
||||
printf("TEST %s %s:%s\n", test_info->name(),
|
||||
AudioInputFile().c_str(), AudioOutputFile().c_str());
|
||||
}
|
||||
|
||||
|
||||
using LowBandwidthAudioTest = CallTest;
|
||||
|
||||
TEST_F(LowBandwidthAudioTest, GoodNetworkHighBitrate) {
|
||||
AudioQualityTest test;
|
||||
RunBaseTest(&test);
|
||||
}
|
||||
|
||||
|
||||
class Mobile2GNetworkTest : public AudioQualityTest {
|
||||
void ModifyAudioConfigs(AudioSendStream::Config* send_config,
|
||||
std::vector<AudioReceiveStream::Config>* receive_configs) override {
|
||||
send_config->send_codec_spec.codec_inst = CodecInst{
|
||||
120, // pltype
|
||||
"OPUS", // plname
|
||||
48000, // plfreq
|
||||
2880, // pacsize
|
||||
1, // channels
|
||||
6000 // rate bits/sec
|
||||
};
|
||||
}
|
||||
|
||||
FakeNetworkPipe::Config GetNetworkPipeConfig() override {
|
||||
FakeNetworkPipe::Config pipe_config;
|
||||
pipe_config.link_capacity_kbps = 12;
|
||||
pipe_config.queue_length_packets = 1500;
|
||||
pipe_config.queue_delay_ms = 400;
|
||||
return pipe_config;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LowBandwidthAudioTest, Mobile2GNetwork) {
|
||||
Mobile2GNetworkTest test;
|
||||
RunBaseTest(&test);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
61
webrtc/audio/test/low_bandwidth_audio_test.h
Normal file
61
webrtc/audio/test/low_bandwidth_audio_test.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
#ifndef WEBRTC_AUDIO_TEST_LOW_BANDWIDTH_AUDIO_TEST_H_
|
||||
#define WEBRTC_AUDIO_TEST_LOW_BANDWIDTH_AUDIO_TEST_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "webrtc/test/call_test.h"
|
||||
#include "webrtc/test/fake_audio_device.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
class AudioQualityTest : public test::EndToEndTest {
|
||||
public:
|
||||
AudioQualityTest();
|
||||
|
||||
protected:
|
||||
virtual std::string AudioInputFile();
|
||||
virtual std::string AudioOutputFile();
|
||||
|
||||
virtual FakeNetworkPipe::Config GetNetworkPipeConfig();
|
||||
|
||||
size_t GetNumVideoStreams() const override;
|
||||
size_t GetNumAudioStreams() const override;
|
||||
size_t GetNumFlexfecStreams() const override;
|
||||
|
||||
std::unique_ptr<test::FakeAudioDevice::Capturer> CreateCapturer() override;
|
||||
std::unique_ptr<test::FakeAudioDevice::Renderer> CreateRenderer() override;
|
||||
|
||||
void OnFakeAudioDevicesCreated(
|
||||
test::FakeAudioDevice* send_audio_device,
|
||||
test::FakeAudioDevice* recv_audio_device) override;
|
||||
|
||||
test::PacketTransport* CreateSendTransport(Call* sender_call) override;
|
||||
test::PacketTransport* CreateReceiveTransport() override;
|
||||
|
||||
void ModifyAudioConfigs(
|
||||
AudioSendStream::Config* send_config,
|
||||
std::vector<AudioReceiveStream::Config>* receive_configs) override;
|
||||
|
||||
void PerformTest() override;
|
||||
void OnTestFinished() override;
|
||||
|
||||
private:
|
||||
test::FakeAudioDevice* send_audio_device_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_AUDIO_TEST_LOW_BANDWIDTH_AUDIO_TEST_H_
|
||||
@ -17,6 +17,7 @@ output files will be performed.
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
@ -26,30 +27,96 @@ SRC_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir,
|
||||
os.pardir))
|
||||
|
||||
|
||||
def _RunCommand(argv, cwd=SRC_DIR, **kwargs):
|
||||
logging.info('Running %r', argv)
|
||||
subprocess.check_call(argv, cwd=cwd, **kwargs)
|
||||
def _LogCommand(command):
|
||||
logging.info('Running %r', command)
|
||||
return command
|
||||
|
||||
|
||||
def _ParseArgs():
|
||||
parser = argparse.ArgumentParser(description='Run low-bandwidth audio tests.')
|
||||
parser.add_argument('build_dir',
|
||||
help='Path to the build directory (e.g. out/Release).')
|
||||
parser.add_argument('--remove', action='store_true',
|
||||
help='Remove output audio files after testing.')
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
def _GetPlatform():
|
||||
if sys.platform == 'win32':
|
||||
return 'win'
|
||||
elif sys.platform == 'darwin':
|
||||
return 'mac'
|
||||
elif sys.platform.startswith('linux'):
|
||||
return 'linux'
|
||||
|
||||
|
||||
def _GetExecutableExtension():
|
||||
if sys.platform == 'win32':
|
||||
return '.exe'
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def _DownloadTools():
|
||||
tools_dir = os.path.join(SRC_DIR, 'tools-webrtc')
|
||||
toolchain_dir = os.path.join(tools_dir, 'audio_quality')
|
||||
|
||||
# Download pesq.
|
||||
download_script = os.path.join(tools_dir, 'download_tools.py')
|
||||
command = [sys.executable, download_script, toolchain_dir]
|
||||
subprocess.check_call(_LogCommand(command))
|
||||
|
||||
pesq_path = os.path.join(toolchain_dir, _GetPlatform(),
|
||||
'pesq' + _GetExecutableExtension())
|
||||
return pesq_path
|
||||
|
||||
|
||||
def main():
|
||||
# pylint: disable=W0101
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
args = _ParseArgs()
|
||||
|
||||
test_executable = os.path.join(args.build_dir, 'low_bandwidth_audio_test')
|
||||
if sys.platform == 'win32':
|
||||
test_executable += '.exe'
|
||||
pesq_path = _DownloadTools()
|
||||
|
||||
_RunCommand([test_executable])
|
||||
test_executable_path = os.path.join(args.build_dir,
|
||||
'low_bandwidth_audio_test' + _GetExecutableExtension())
|
||||
|
||||
# Start the test executable that produces audio files.
|
||||
command = [test_executable_path]
|
||||
test_process = subprocess.Popen(_LogCommand(command), stdout=subprocess.PIPE)
|
||||
|
||||
for line in iter(test_process.stdout.readline, ''):
|
||||
# Echo the output to screen.
|
||||
sys.stdout.write(line)
|
||||
|
||||
# Extract specific lines that contain information about produced files.
|
||||
match = re.search(r'^TEST (\w+) ([^:]+?):([^:]+?)\n?$', line)
|
||||
if not match:
|
||||
continue
|
||||
test_name, reference_file, degraded_file = match.groups()
|
||||
|
||||
# Analyze audio
|
||||
command = [pesq_path, '+16000', reference_file, degraded_file]
|
||||
pesq_output = subprocess.check_output(_LogCommand(command))
|
||||
|
||||
if args.remove:
|
||||
os.remove(degraded_file)
|
||||
|
||||
# Find the scores in stdout of pesq.
|
||||
match = re.search(
|
||||
r'Prediction \(Raw MOS, MOS-LQO\):\s+=\s+([\d.]+)\s+([\d.]+)',
|
||||
pesq_output)
|
||||
if match:
|
||||
raw_mos, _ = match.groups()
|
||||
|
||||
# Output a result for the perf dashboard.
|
||||
print 'RESULT pesq_mos: %s= %s score' % (test_name, raw_mos)
|
||||
else:
|
||||
logging.error('PESQ: %s', pesq_output.splitlines()[-1])
|
||||
|
||||
return test_process.wait()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -255,6 +255,7 @@ if (is_ios) {
|
||||
rtc_test("test_support_unittests") {
|
||||
deps = []
|
||||
sources = [
|
||||
"fake_audio_device_unittest.cc",
|
||||
"fake_network_pipe_unittest.cc",
|
||||
"frame_generator_unittest.cc",
|
||||
"rtp_file_reader_unittest.cc",
|
||||
@ -409,6 +410,9 @@ rtc_source_set("test_common") {
|
||||
"//testing/gmock",
|
||||
"//testing/gtest",
|
||||
]
|
||||
if (!is_android) {
|
||||
deps += [ "../modules/video_capture:video_capture_internal_impl" ]
|
||||
}
|
||||
}
|
||||
|
||||
config("test_renderer_exported_config") {
|
||||
|
||||
@ -50,6 +50,9 @@ void CallTest::RunBaseTest(BaseTest* test) {
|
||||
RTC_DCHECK(num_video_streams_ > 0 || num_audio_streams_ > 0);
|
||||
Call::Config send_config(test->GetSenderCallConfig());
|
||||
if (num_audio_streams_ > 0) {
|
||||
CreateFakeAudioDevices(test->CreateCapturer(), test->CreateRenderer());
|
||||
test->OnFakeAudioDevicesCreated(fake_send_audio_device_.get(),
|
||||
fake_recv_audio_device_.get());
|
||||
CreateVoiceEngines();
|
||||
AudioState::Config audio_state_config;
|
||||
audio_state_config.voice_engine = voe_send_.voice_engine;
|
||||
@ -132,6 +135,8 @@ void CallTest::RunBaseTest(BaseTest* test) {
|
||||
DestroyCalls();
|
||||
if (num_audio_streams_ > 0)
|
||||
DestroyVoiceEngines();
|
||||
|
||||
test->OnTestFinished();
|
||||
}
|
||||
|
||||
void CallTest::Start() {
|
||||
@ -298,11 +303,13 @@ void CallTest::CreateFrameGeneratorCapturer(int framerate,
|
||||
VideoSendStream::DegradationPreference::kBalanced);
|
||||
}
|
||||
|
||||
void CallTest::CreateFakeAudioDevices() {
|
||||
void CallTest::CreateFakeAudioDevices(
|
||||
std::unique_ptr<FakeAudioDevice::Capturer> capturer,
|
||||
std::unique_ptr<FakeAudioDevice::Renderer> renderer) {
|
||||
fake_send_audio_device_.reset(new FakeAudioDevice(
|
||||
FakeAudioDevice::CreatePulsedNoiseCapturer(256, 48000), nullptr, 1.f));
|
||||
std::move(capturer), nullptr, 1.f));
|
||||
fake_recv_audio_device_.reset(new FakeAudioDevice(
|
||||
nullptr, FakeAudioDevice::CreateDiscardRenderer(48000), 1.f));
|
||||
nullptr, std::move(renderer), 1.f));
|
||||
}
|
||||
|
||||
void CallTest::CreateVideoStreams() {
|
||||
@ -361,7 +368,6 @@ void CallTest::DestroyStreams() {
|
||||
}
|
||||
|
||||
void CallTest::CreateVoiceEngines() {
|
||||
CreateFakeAudioDevices();
|
||||
voe_send_.voice_engine = VoiceEngine::Create();
|
||||
voe_send_.base = VoEBase::GetInterface(voe_send_.voice_engine);
|
||||
EXPECT_EQ(0, voe_send_.base->Init(fake_send_audio_device_.get(), nullptr,
|
||||
@ -427,6 +433,18 @@ BaseTest::BaseTest(unsigned int timeout_ms) : RtpRtcpObserver(timeout_ms) {
|
||||
BaseTest::~BaseTest() {
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeAudioDevice::Capturer> BaseTest::CreateCapturer() {
|
||||
return FakeAudioDevice::CreatePulsedNoiseCapturer(256, 48000);
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeAudioDevice::Renderer> BaseTest::CreateRenderer() {
|
||||
return FakeAudioDevice::CreateDiscardRenderer(48000);
|
||||
}
|
||||
|
||||
void BaseTest::OnFakeAudioDevicesCreated(FakeAudioDevice* send_audio_device,
|
||||
FakeAudioDevice* recv_audio_device) {
|
||||
}
|
||||
|
||||
Call::Config BaseTest::GetSenderCallConfig() {
|
||||
return Call::Config(&event_log_);
|
||||
}
|
||||
@ -491,6 +509,9 @@ void BaseTest::OnFrameGeneratorCapturerCreated(
|
||||
FrameGeneratorCapturer* frame_generator_capturer) {
|
||||
}
|
||||
|
||||
void BaseTest::OnTestFinished() {
|
||||
}
|
||||
|
||||
SendTest::SendTest(unsigned int timeout_ms) : BaseTest(timeout_ms) {
|
||||
}
|
||||
|
||||
|
||||
@ -83,7 +83,9 @@ class CallTest : public ::testing::Test {
|
||||
int width,
|
||||
int height);
|
||||
void CreateFrameGeneratorCapturer(int framerate, int width, int height);
|
||||
void CreateFakeAudioDevices();
|
||||
void CreateFakeAudioDevices(
|
||||
std::unique_ptr<FakeAudioDevice::Capturer> capturer,
|
||||
std::unique_ptr<FakeAudioDevice::Renderer> renderer);
|
||||
|
||||
void CreateVideoStreams();
|
||||
void CreateAudioStreams();
|
||||
@ -161,6 +163,11 @@ class BaseTest : public RtpRtcpObserver {
|
||||
virtual size_t GetNumAudioStreams() const;
|
||||
virtual size_t GetNumFlexfecStreams() const;
|
||||
|
||||
virtual std::unique_ptr<FakeAudioDevice::Capturer> CreateCapturer();
|
||||
virtual std::unique_ptr<FakeAudioDevice::Renderer> CreateRenderer();
|
||||
virtual void OnFakeAudioDevicesCreated(FakeAudioDevice* send_audio_device,
|
||||
FakeAudioDevice* recv_audio_device);
|
||||
|
||||
virtual Call::Config GetSenderCallConfig();
|
||||
virtual Call::Config GetReceiverCallConfig();
|
||||
virtual void OnCallsCreated(Call* sender_call, Call* receiver_call);
|
||||
@ -194,6 +201,8 @@ class BaseTest : public RtpRtcpObserver {
|
||||
virtual void OnFrameGeneratorCapturerCreated(
|
||||
FrameGeneratorCapturer* frame_generator_capturer);
|
||||
|
||||
virtual void OnTestFinished();
|
||||
|
||||
webrtc::RtcEventLogNullImpl event_log_;
|
||||
};
|
||||
|
||||
|
||||
@ -111,6 +111,69 @@ class WavFileWriter final : public test::FakeAudioDevice::Renderer {
|
||||
WavWriter wav_writer_;
|
||||
};
|
||||
|
||||
class BoundedWavFileWriter : public test::FakeAudioDevice::Renderer {
|
||||
public:
|
||||
BoundedWavFileWriter(std::string filename, int sampling_frequency_in_hz)
|
||||
: sampling_frequency_in_hz_(sampling_frequency_in_hz),
|
||||
wav_writer_(filename, sampling_frequency_in_hz, 1),
|
||||
silent_audio_(test::FakeAudioDevice::SamplesPerFrame(
|
||||
sampling_frequency_in_hz), 0),
|
||||
started_writing_(false),
|
||||
trailing_zeros_(0) {}
|
||||
|
||||
int SamplingFrequency() const override {
|
||||
return sampling_frequency_in_hz_;
|
||||
}
|
||||
|
||||
bool Render(rtc::ArrayView<const int16_t> data) override {
|
||||
const int16_t kAmplitudeThreshold = 5;
|
||||
|
||||
const int16_t* begin = data.begin();
|
||||
const int16_t* end = data.end();
|
||||
if (!started_writing_) {
|
||||
// Cut off silence at the beginning.
|
||||
while (begin < end) {
|
||||
if (std::abs(*begin) > kAmplitudeThreshold) {
|
||||
started_writing_ = true;
|
||||
break;
|
||||
}
|
||||
++begin;
|
||||
}
|
||||
}
|
||||
if (started_writing_) {
|
||||
// Cut off silence at the end.
|
||||
while (begin < end) {
|
||||
if (*(end - 1) != 0) {
|
||||
break;
|
||||
}
|
||||
--end;
|
||||
}
|
||||
if (begin < end) {
|
||||
// If it turns out that the silence was not final, need to write all the
|
||||
// skipped zeros and continue writing audio.
|
||||
while (trailing_zeros_ > 0) {
|
||||
const size_t zeros_to_write = std::min(trailing_zeros_,
|
||||
silent_audio_.size());
|
||||
wav_writer_.WriteSamples(silent_audio_.data(), zeros_to_write);
|
||||
trailing_zeros_ -= zeros_to_write;
|
||||
}
|
||||
wav_writer_.WriteSamples(begin, end - begin);
|
||||
}
|
||||
// Save the number of zeros we skipped in case this needs to be restored.
|
||||
trailing_zeros_ += data.end() - end;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
int sampling_frequency_in_hz_;
|
||||
WavWriter wav_writer_;
|
||||
std::vector<int16_t> silent_audio_;
|
||||
bool started_writing_;
|
||||
size_t trailing_zeros_;
|
||||
};
|
||||
|
||||
|
||||
class DiscardRenderer final : public test::FakeAudioDevice::Renderer {
|
||||
public:
|
||||
explicit DiscardRenderer(int sampling_frequency_in_hz)
|
||||
@ -161,6 +224,13 @@ std::unique_ptr<FakeAudioDevice::Renderer> FakeAudioDevice::CreateWavFileWriter(
|
||||
new WavFileWriter(filename, sampling_frequency_in_hz));
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeAudioDevice::Renderer>
|
||||
FakeAudioDevice::CreateBoundedWavFileWriter(
|
||||
std::string filename, int sampling_frequency_in_hz) {
|
||||
return std::unique_ptr<FakeAudioDevice::Renderer>(
|
||||
new BoundedWavFileWriter(filename, sampling_frequency_in_hz));
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeAudioDevice::Renderer>
|
||||
FakeAudioDevice::CreateDiscardRenderer(int sampling_frequency_in_hz) {
|
||||
return std::unique_ptr<FakeAudioDevice::Renderer>(
|
||||
|
||||
@ -89,6 +89,12 @@ class FakeAudioDevice : public FakeAudioDeviceModule {
|
||||
static std::unique_ptr<Renderer> CreateWavFileWriter(
|
||||
std::string filename, int sampling_frequency_in_hz);
|
||||
|
||||
// Returns a Renderer instance that writes its data to a WAV file, cutting
|
||||
// off silence at the beginning (not necessarily perfect silence, see
|
||||
// kAmplitudeThreshold) and at the end (only actual 0 samples in this case).
|
||||
static std::unique_ptr<Renderer> CreateBoundedWavFileWriter(
|
||||
std::string filename, int sampling_frequency_in_hz);
|
||||
|
||||
// Returns a Renderer instance that does nothing with the audio data.
|
||||
static std::unique_ptr<Renderer> CreateDiscardRenderer(
|
||||
int sampling_frequency_in_hz);
|
||||
|
||||
131
webrtc/test/fake_audio_device_unittest.cc
Normal file
131
webrtc/test/fake_audio_device_unittest.cc
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "webrtc/common_audio/wav_file.h"
|
||||
#include "webrtc/common_audio/wav_header.h"
|
||||
#include "webrtc/test/fake_audio_device.h"
|
||||
#include "webrtc/test/gtest.h"
|
||||
#include "webrtc/test/testsupport/fileutils.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
void RunTest(const std::vector<int16_t>& input_samples,
|
||||
const std::vector<int16_t>& expected_samples,
|
||||
size_t samples_per_frame) {
|
||||
const ::testing::TestInfo* const test_info =
|
||||
::testing::UnitTest::GetInstance()->current_test_info();
|
||||
|
||||
const std::string output_filename = test::OutputPath() +
|
||||
"BoundedWavFileWriterTest_" + test_info->name() + ".wav";
|
||||
|
||||
static const size_t kSamplesPerFrame = 8;
|
||||
static const int kSampleRate = kSamplesPerFrame * 100;
|
||||
EXPECT_EQ(FakeAudioDevice::SamplesPerFrame(kSampleRate), kSamplesPerFrame);
|
||||
|
||||
{
|
||||
std::unique_ptr<FakeAudioDevice::Renderer> writer =
|
||||
FakeAudioDevice::CreateBoundedWavFileWriter(output_filename, 800);
|
||||
|
||||
for (size_t i = 0; i < input_samples.size(); i += kSamplesPerFrame) {
|
||||
EXPECT_TRUE(writer->Render(rtc::ArrayView<const int16_t>(
|
||||
&input_samples[i],
|
||||
std::min(kSamplesPerFrame, input_samples.size() - i))));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
WavReader reader(output_filename);
|
||||
std::vector<int16_t> read_samples(expected_samples.size());
|
||||
EXPECT_EQ(expected_samples.size(),
|
||||
reader.ReadSamples(read_samples.size(), read_samples.data()));
|
||||
EXPECT_EQ(expected_samples, read_samples);
|
||||
|
||||
EXPECT_EQ(0u, reader.ReadSamples(read_samples.size(), read_samples.data()));
|
||||
}
|
||||
|
||||
remove(output_filename.c_str());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(BoundedWavFileWriterTest, NoSilence) {
|
||||
static const std::vector<int16_t> kInputSamples = {
|
||||
75, 1234, 243, -1231, -22222, 0, 3, 88,
|
||||
1222, -1213, -13222, -7, -3525, 5787, -25247, 8
|
||||
};
|
||||
static const std::vector<int16_t> kExpectedSamples = kInputSamples;
|
||||
RunTest(kInputSamples, kExpectedSamples, 8);
|
||||
}
|
||||
|
||||
TEST(BoundedWavFileWriterTest, SomeStartSilence) {
|
||||
static const std::vector<int16_t> kInputSamples = {
|
||||
0, 0, 0, 0, 3, 0, 0, 0,
|
||||
0, 3, -13222, -7, -3525, 5787, -25247, 8
|
||||
};
|
||||
static const std::vector<int16_t> kExpectedSamples(kInputSamples.begin() + 10,
|
||||
kInputSamples.end());
|
||||
RunTest(kInputSamples, kExpectedSamples, 8);
|
||||
}
|
||||
|
||||
TEST(BoundedWavFileWriterTest, NegativeStartSilence) {
|
||||
static const std::vector<int16_t> kInputSamples = {
|
||||
0, -4, -6, 0, 3, 0, 0, 0,
|
||||
0, 3, -13222, -7, -3525, 5787, -25247, 8
|
||||
};
|
||||
static const std::vector<int16_t> kExpectedSamples(kInputSamples.begin() + 2,
|
||||
kInputSamples.end());
|
||||
RunTest(kInputSamples, kExpectedSamples, 8);
|
||||
}
|
||||
|
||||
TEST(BoundedWavFileWriterTest, SomeEndSilence) {
|
||||
static const std::vector<int16_t> kInputSamples = {
|
||||
75, 1234, 243, -1231, -22222, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
static const std::vector<int16_t> kExpectedSamples(kInputSamples.begin(),
|
||||
kInputSamples.end() - 9);
|
||||
RunTest(kInputSamples, kExpectedSamples, 8);
|
||||
}
|
||||
|
||||
TEST(BoundedWavFileWriterTest, DoubleEndSilence) {
|
||||
static const std::vector<int16_t> kInputSamples = {
|
||||
75, 1234, 243, -1231, -22222, 0, 0, 0,
|
||||
0, -1213, -13222, -7, -3525, 5787, 0, 0
|
||||
};
|
||||
static const std::vector<int16_t> kExpectedSamples(kInputSamples.begin(),
|
||||
kInputSamples.end() - 2);
|
||||
RunTest(kInputSamples, kExpectedSamples, 8);
|
||||
}
|
||||
|
||||
TEST(BoundedWavFileWriterTest, DoubleSilence) {
|
||||
static const std::vector<int16_t> kInputSamples = {
|
||||
0, -1213, -13222, -7, -3525, 5787, 0, 0
|
||||
};
|
||||
static const std::vector<int16_t> kExpectedSamples(kInputSamples.begin() + 1,
|
||||
kInputSamples.end() - 2);
|
||||
RunTest(kInputSamples, kExpectedSamples, 8);
|
||||
}
|
||||
|
||||
TEST(BoundedWavFileWriterTest, EndSilenceCutoff) {
|
||||
static const std::vector<int16_t> kInputSamples = {
|
||||
75, 1234, 243, -1231, -22222, 0, 1, 0,
|
||||
0, 0, 0
|
||||
};
|
||||
static const std::vector<int16_t> kExpectedSamples(kInputSamples.begin(),
|
||||
kInputSamples.end() - 4);
|
||||
RunTest(kInputSamples, kExpectedSamples, 8);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
@ -93,9 +93,6 @@ if (rtc_include_tests) {
|
||||
"//webrtc/test:test_renderer",
|
||||
"//webrtc/test:video_test_common",
|
||||
]
|
||||
if (!is_android) {
|
||||
deps += [ "../modules/video_capture:video_capture_internal_impl" ]
|
||||
}
|
||||
if (!build_with_chromium && is_clang) {
|
||||
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
|
||||
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
|
||||
@ -178,9 +175,6 @@ if (rtc_include_tests) {
|
||||
"../test:test_renderer",
|
||||
"//third_party/gflags",
|
||||
]
|
||||
if (!is_android) {
|
||||
deps += [ "../modules/video_capture:video_capture_internal_impl" ]
|
||||
}
|
||||
if (!build_with_chromium && is_clang) {
|
||||
# Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
|
||||
suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user