From b06db96840602a4b753c22f2c818c07490a3319d Mon Sep 17 00:00:00 2001 From: "andrew@webrtc.org" Date: Thu, 10 May 2012 18:45:11 +0000 Subject: [PATCH] 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 --- tools/e2e_quality/audio/audio_e2e_harness.cc | 77 ++++++++++++++++ tools/e2e_quality/audio/default.pa | 6 ++ tools/e2e_quality/audio/run_audio_test.py | 94 ++++++++++++++++++++ tools/e2e_quality/e2e_quality.gyp | 25 ++++++ webrtc.gyp | 3 +- 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 tools/e2e_quality/audio/audio_e2e_harness.cc create mode 100755 tools/e2e_quality/audio/default.pa create mode 100755 tools/e2e_quality/audio/run_audio_test.py create mode 100644 tools/e2e_quality/e2e_quality.gyp diff --git a/tools/e2e_quality/audio/audio_e2e_harness.cc b/tools/e2e_quality/audio/audio_e2e_harness.cc new file mode 100644 index 0000000000..2899b42140 --- /dev/null +++ b/tools/e2e_quality/audio/audio_e2e_harness.cc @@ -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(); +} diff --git a/tools/e2e_quality/audio/default.pa b/tools/e2e_quality/audio/default.pa new file mode 100755 index 0000000000..adef2dbe50 --- /dev/null +++ b/tools/e2e_quality/audio/default.pa @@ -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 diff --git a/tools/e2e_quality/audio/run_audio_test.py b/tools/e2e_quality/audio/run_audio_test.py new file mode 100755 index 0000000000..14895f9aee --- /dev/null +++ b/tools/e2e_quality/audio/run_audio_test.py @@ -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)) diff --git a/tools/e2e_quality/e2e_quality.gyp b/tools/e2e_quality/e2e_quality.gyp new file mode 100644 index 0000000000..fc04bdf34b --- /dev/null +++ b/tools/e2e_quality/e2e_quality.gyp @@ -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', + ], + }, + ], +} diff --git a/webrtc.gyp b/webrtc.gyp index e18399bd3a..38e57b1f6f 100644 --- a/webrtc.gyp +++ b/webrtc.gyp @@ -21,9 +21,8 @@ 'src/voice_engine/voice_engine.gyp:*', 'test/metrics.gyp:*', 'test/test.gyp:*', + 'tools/e2e_quality/e2e_quality.gyp:*', ], }, ], - 'conditions': [ - ], # conditions }