Add a framework for audio end-to-end quality testing.

The quality comparison step is still to be done.

BUG=issue502
TEST=manual

Review URL: https://webrtc-codereview.appspot.com/577005

git-svn-id: http://webrtc.googlecode.com/svn/trunk@2220 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
andrew@webrtc.org 2012-05-10 18:45:11 +00:00
parent 8aaf31d14f
commit b06db96840
5 changed files with 203 additions and 2 deletions

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2012 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.
*/
// Sets up a simple VoiceEngine loopback call with the default audio devices
// and runs forever. Some parameters can be configured through command-line
// flags.
#include "gflags/gflags.h"
#include "gtest/gtest.h"
#include "src/voice_engine/main/interface/voe_audio_processing.h"
#include "src/voice_engine/main/interface/voe_base.h"
#include "src/voice_engine/main/interface/voe_codec.h"
DEFINE_string(codec, "ISAC", "codec name");
DEFINE_int32(rate, 16000, "codec sample rate in Hz");
namespace webrtc {
namespace {
void RunHarness() {
VoiceEngine* voe = VoiceEngine::Create();
ASSERT_TRUE(voe != NULL);
VoEAudioProcessing* audio = VoEAudioProcessing::GetInterface(voe);
ASSERT_TRUE(audio != NULL);
VoEBase* base = VoEBase::GetInterface(voe);
ASSERT_TRUE(base != NULL);
VoECodec* codec = VoECodec::GetInterface(voe);
ASSERT_TRUE(codec != NULL);
ASSERT_EQ(0, base->Init());
int channel = base->CreateChannel();
ASSERT_NE(-1, channel);
ASSERT_EQ(0, base->SetSendDestination(channel, 1234, "127.0.0.1"));
ASSERT_EQ(0, base->SetLocalReceiver(channel, 1234));
CodecInst codec_params = {0};
bool codec_found = false;
for (int i = 0; i < codec->NumOfCodecs(); i++) {
ASSERT_EQ(0, codec->GetCodec(i, codec_params));
if (FLAGS_codec.compare(codec_params.plname) == 0 &&
FLAGS_rate == codec_params.plfreq) {
codec_found = true;
break;
}
}
ASSERT_TRUE(codec_found);
ASSERT_EQ(0, codec->SetSendCodec(channel, codec_params));
// Disable all audio processing.
ASSERT_EQ(0, audio->SetAgcStatus(false));
ASSERT_EQ(0, audio->SetEcStatus(false));
ASSERT_EQ(0, audio->EnableHighPassFilter(false));
ASSERT_EQ(0, audio->SetNsStatus(false));
ASSERT_EQ(0, base->StartReceive(channel));
ASSERT_EQ(0, base->StartPlayout(channel));
ASSERT_EQ(0, base->StartSend(channel));
// Run forever...
while (1);
}
} // namespace
} // namespace webrtc
int main(int argc, char** argv) {
google::ParseCommandLineFlags(&argc, &argv, true);
webrtc::RunHarness();
}

View File

@ -0,0 +1,6 @@
# Place in ~/.pulse/ to add null sinks for the audio end-to-end quality test.
.include /etc/pulse/default.pa
load-module module-null-sink sink_name=render sink_properties=device.description=render format=s16 rate=48000 channels=1
load-module module-null-sink sink_name=capture sink_properties=device.description=capture format=s16 rate=48000 channels=1

View File

@ -0,0 +1,94 @@
#!/usr/bin/env python
# Copyright (c) 2012 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.
__author__ = 'andrew@webrtc.org (Andrew MacDonald)'
import optparse
import os
import shlex
import subprocess
import sys
import threading
import time
"""Runs an end-to-end audio quality test on Linux.
Expects the presence of PulseAudio virtual devices (null sinks). These are
configured as default devices for a VoiceEngine audio call. A PulseAudio
utility (pacat) is used to play to and record from the virtual devices.
The input reference file is then compared to the output file.
"""
def popen_and_call(popen_args, call_on_exit):
"""Executes the arguments, and triggers the callback when finished."""
def run_in_thread(popen_args, call_on_exit):
proc = subprocess.Popen(popen_args)
proc.wait()
call_on_exit()
return
thread = threading.Thread(target=run_in_thread,
args=(popen_args, call_on_exit))
thread.start()
return thread
def main(argv):
parser = optparse.OptionParser()
usage = 'Usage: %prog [options]'
parser.set_usage(usage)
parser.add_option('--input', default='input.pcm', help='input PCM file')
parser.add_option('--output', default='output.pcm', help='output PCM file')
parser.add_option('--codec', default='ISAC', help='codec name')
parser.add_option('--rate', default='16000', help='sample rate in Hz')
parser.add_option('--channels', default='1', help='number of channels')
parser.add_option('--play_sink', default='capture',
help='name of PulseAudio sink to which to play audio')
parser.add_option('--rec_sink', default='render',
help='name of PulseAudio sink whose monitor will be recorded')
parser.add_option('--harness',
default=os.path.dirname(sys.argv[0]) +
'/../../../out/Debug/audio_e2e_harness',
help='path to audio harness executable')
(options, args) = parser.parse_args(argv[1:])
# Set default devices to be used by VoiceEngine.
subprocess.call(['pacmd', 'set-default-sink', options.rec_sink]);
subprocess.call(['pacmd', 'set-default-source',
options.play_sink + '.monitor']);
print 'Start an audio call'
print options.harness
voe_proc = subprocess.Popen([options.harness,
'--codec=' + options.codec, '--rate=' + options.rate]);
print 'Start recording to ' + options.output
format_args = ('-n --format=s16le --rate=' + options.rate + ' --channels=' +
options.channels + ' --raw')
command = ('pacat -r -d ' + options.rec_sink + '.monitor ' + format_args +
' ' + options.output)
record_proc = subprocess.Popen(shlex.split(command))
def stop_recording():
record_proc.kill()
print 'Start playing from ' + options.input
command = ('pacat -p -d ' + options.play_sink + ' ' + format_args + ' ' +
options.input)
popen_and_call(shlex.split(command), stop_recording)
# record_proc will be killed after playout finishes.
record_proc.wait()
print 'Shutdown audio call'
voe_proc.kill()
# TODO(andrew): compare files.
if __name__ == '__main__':
sys.exit(main(sys.argv))

View File

@ -0,0 +1,25 @@
# Copyright (c) 2012 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.
{
'includes': [ '../../src/build/common.gypi'],
'targets': [
{
'target_name': 'audio_e2e_harness',
'type': 'executable',
'dependencies': [
'<(webrtc_root)/voice_engine/voice_engine.gyp:voice_engine_core',
'<(DEPTH)/testing/gtest.gyp:gtest',
'<(DEPTH)/third_party/google-gflags/google-gflags.gyp:google-gflags',
],
'sources': [
'audio/audio_e2e_harness.cc',
],
},
],
}

View File

@ -21,9 +21,8 @@
'src/voice_engine/voice_engine.gyp:*',
'test/metrics.gyp:*',
'test/test.gyp:*',
'tools/e2e_quality/e2e_quality.gyp:*',
],
},
],
'conditions': [
], # conditions
}