Add loopback latency test

Add loopback_latency tool to audioloop deps, create a new
audiovideo_LoopabackLatency test to test the latency loop from
playback to capture.

Since this is the initial version of latency test. Right now
the test only examines loopback latency from playback to capture
port through cras alsa plugin. More test configurations will be
added in later changes.

CQ-DEPEND=33435

BUG=chromium-os:32898
TEST=Run audiovideo_LoopbackLatency test passes on Lumpy

Change-Id: I6884a9d7ac779280b4d7b10287162fa5142488e7
Reviewed-on: https://gerrit.chromium.org/gerrit/29413
Commit-Ready: Hsinyu Chao <hychao@chromium.org>
Reviewed-by: Hsinyu Chao <hychao@chromium.org>
Tested-by: Hsinyu Chao <hychao@chromium.org>
diff --git a/client/deps/audioloop/audioloop.py b/client/deps/audioloop/audioloop.py
index 36a89c6..e040310 100755
--- a/client/deps/audioloop/audioloop.py
+++ b/client/deps/audioloop/audioloop.py
@@ -12,6 +12,7 @@
 def setup(topdir):
     srcdir = os.path.join(topdir, 'src')
     os.chdir(srcdir)
+    os.system('make clean')
     utils.make()
     os.chdir(topdir)
 
diff --git a/client/deps/audioloop/src/Makefile b/client/deps/audioloop/src/Makefile
index ade88a9..dc38d80 100644
--- a/client/deps/audioloop/src/Makefile
+++ b/client/deps/audioloop/src/Makefile
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 
-LDFLAGS = -lasound -lpthread -lrt \
+LDFLAGS = -lasound -lcras -lm -lpthread -lrt \
 	 -L$(SYSROOT)/lib \
 	 -L$(SYSROOT)/usr/lib
 
@@ -14,7 +14,7 @@
 
 .PHONY: clean run all install
 
-all: looptest
+all: looptest loopback_latency
 
 looptest: $(OFILES)
 
@@ -22,6 +22,8 @@
 
 looptest.o: looptest.c libaudiodev.h
 
+loopback_latency: loopback_latency.c
+
 install: looptest
 	install -m 0755 looptest $(SYSROOT)/usr/local/bin
 
@@ -29,4 +31,4 @@
 	./looptest
 
 clean:
-	rm -rf looptest $(OFILES)
+	rm -rf looptest loopback_latency $(OFILES)
diff --git a/client/deps/audioloop/src/loopback_latency.c b/client/deps/audioloop/src/loopback_latency.c
new file mode 100644
index 0000000..b08f313
--- /dev/null
+++ b/client/deps/audioloop/src/loopback_latency.c
@@ -0,0 +1,581 @@
+/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <alsa/asoundlib.h>
+#include <sys/time.h>
+#include <math.h>
+
+#include "cras_client.h"
+
+static double phase = M_PI / 2;
+static unsigned rate = 48000;
+static unsigned channels = 2;
+static snd_pcm_uframes_t buffer_frames = 480;
+static snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
+
+static int cras_put_silent = 5;
+static int cras_captured_noise = 0;
+static struct timeval *cras_play_time = NULL;
+static struct timeval *cras_cap_time = NULL;
+static int noise_threshold = 0x4000;
+
+
+static void generate_sine(const snd_pcm_channel_area_t *areas,
+                          snd_pcm_uframes_t offset, int count,
+                          double *_phase)
+{
+    static double max_phase = 2. * M_PI;
+    double phase = *_phase;
+    double step = max_phase * 1000 / (double)rate;
+    unsigned char *samples[channels];
+    int steps[channels];
+    unsigned int chn;
+    int format_bits = snd_pcm_format_width(format);
+    unsigned int maxval = (1 << (format_bits - 1)) - 1;
+    int bps = format_bits / 8;  /* bytes per sample */
+    int phys_bps = snd_pcm_format_physical_width(format) / 8;
+    int big_endian = snd_pcm_format_big_endian(format) == 1;
+    int to_unsigned = snd_pcm_format_unsigned(format) == 1;
+    int is_float = (format == SND_PCM_FORMAT_FLOAT_LE ||
+            format == SND_PCM_FORMAT_FLOAT_BE);
+
+    /* Verify and prepare the contents of areas */
+    for (chn = 0; chn < channels; chn++) {
+        if ((areas[chn].first % 8) != 0) {
+            fprintf(stderr, "areas[%i].first == %i, aborting...\n", chn,
+                    areas[chn].first);
+            exit(EXIT_FAILURE);
+        }
+        if ((areas[chn].step % 16) != 0) {
+            fprintf(stderr, "areas[%i].step == %i, aborting...\n", chn, areas
+                    [chn].step);
+            exit(EXIT_FAILURE);
+        }
+        steps[chn] = areas[chn].step / 8;
+        samples[chn] = ((unsigned char *)areas[chn].addr) +
+                (areas[chn].first / 8) + offset * steps[chn];
+    }
+
+    /* Fill the channel areas */
+    while (count-- > 0) {
+        union {
+            float f;
+            int i;
+        } fval;
+        int res, i;
+        if (is_float) {
+            fval.f = sin(phase) * maxval;
+            res = fval.i;
+        } else
+            res = sin(phase) * maxval;
+        if (to_unsigned)
+            res ^= 1U << (format_bits - 1);
+        for (chn = 0; chn < channels; chn++) {
+            /* Generate data in native endian format */
+            if (big_endian) {
+                for (i = 0; i < bps; i++)
+                    *(samples[chn] + phys_bps - 1 - i) = (res >> i * 8) & 0xff;
+            } else {
+                for (i = 0; i < bps; i++)
+                    *(samples[chn] + i) = (res >>  i * 8) & 0xff;
+            }
+            samples[chn] += steps[chn];
+        }
+        phase += step;
+        if (phase >= max_phase)
+            phase -= max_phase;
+    }
+    *_phase = phase;
+}
+
+static void config_pcm(snd_pcm_t *handle,
+                       unsigned int rate,
+                       unsigned int channels,
+                       snd_pcm_format_t format,
+                       snd_pcm_uframes_t *buffer_size,
+                       snd_pcm_uframes_t *period_size)
+{
+    int err;
+    snd_pcm_hw_params_t *hw_params;
+
+    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
+        fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {
+        fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    if ((err = snd_pcm_hw_params_set_access(handle, hw_params,
+            SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+        fprintf(stderr, "cannot set access type (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    if ((err = snd_pcm_hw_params_set_format(handle, hw_params,
+            format)) < 0) {
+        fprintf(stderr, "cannot set sample format (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    if ((err = snd_pcm_hw_params_set_rate_near(
+            handle, hw_params, &rate, 0)) < 0) {
+        fprintf(stderr, "cannot set sample rate (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    if ((err = snd_pcm_hw_params_set_channels(handle, hw_params, 2)) < 0) {
+        fprintf(stderr, "cannot set channel count (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    if ((err = snd_pcm_hw_params_set_buffer_size_near(
+            handle, hw_params, buffer_size)) < 0) {
+        fprintf(stderr, "cannot set channel count (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    *period_size = *buffer_size / 2;
+    if ((err = snd_pcm_hw_params_set_period_size_near(
+            handle, hw_params, period_size, 0)) < 0) {
+        fprintf(stderr, "cannot set channel count (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    if ((err = snd_pcm_hw_params(handle, hw_params)) < 0) {
+        fprintf(stderr, "cannot set parameters (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    snd_pcm_hw_params_free(hw_params);
+
+    if ((err = snd_pcm_prepare(handle)) < 0) {
+        fprintf(stderr, "cannot prepare audio interface for use (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+}
+
+static int capture_some(snd_pcm_t *pcm, short *buf, unsigned len)
+{
+    snd_pcm_sframes_t frames = snd_pcm_avail(pcm);
+    int err;
+
+    if (frames > 0) {
+        frames = frames > len ? len : frames;
+
+        if ((err = snd_pcm_readi(pcm, buf, frames)) != frames) {
+            fprintf(stderr, "read from audio interface failed (%s)\n",
+                    snd_strerror(err));
+            exit(1);
+        }
+    }
+
+    return (int)frames;
+}
+
+static int check_for_noise(short *buf, unsigned len, unsigned channels)
+{
+    unsigned int i;
+    for (i = 0; i < len * channels; i++)
+        if (abs(buf[i]) > noise_threshold)
+            return i;
+    return -1;
+}
+
+static unsigned long subtract_timevals(const struct timeval *end,
+                                       const struct timeval *beg)
+{
+    struct timeval diff;
+    /* If end is before geb, return 0. */
+    if ((end->tv_sec < beg->tv_sec) ||
+            ((end->tv_sec == beg->tv_sec) && (end->tv_usec <= beg->tv_usec)))
+        diff.tv_sec = diff.tv_usec = 0;
+    else {
+        if (end->tv_usec < beg->tv_usec) {
+            diff.tv_sec = end->tv_sec - beg->tv_sec - 1;
+            diff.tv_usec =
+                end->tv_usec + 1000000L - beg->tv_usec;
+        } else {
+            diff.tv_sec = end->tv_sec - beg->tv_sec;
+            diff.tv_usec = end->tv_usec - beg->tv_usec;
+        }
+    }
+    return diff.tv_sec * 1000000 + diff.tv_usec;
+}
+
+static int cras_capture_tone(struct cras_client *client,
+                             cras_stream_id_t stream_id,
+                             uint8_t *samples, size_t frames,
+                             const struct timespec *sample_time,
+                             void *arg)
+{
+    assert(snd_pcm_format_physical_width(format) == 16);
+
+    short *data = (short *)samples;
+
+    int cap_frames = check_for_noise(data, frames, channels);
+    if (cap_frames > 0 && !cras_captured_noise) {
+        fprintf(stderr, "Got noise\n");
+        cras_captured_noise = 1;
+
+        cap_frames /= channels;
+        struct timespec shifted_time = *sample_time;
+        shifted_time.tv_nsec += 1000000000L / rate * cap_frames;
+        while (shifted_time.tv_nsec > 1000000000L) {
+            shifted_time.tv_sec++;
+            shifted_time.tv_nsec -= 1000000000L;
+        }
+        cras_client_calc_capture_latency(&shifted_time, (struct timespec*)arg);
+        cras_cap_time = (struct timeval*)malloc(sizeof(*cras_cap_time));
+        gettimeofday(cras_cap_time, NULL);
+    }
+
+    return frames;
+}
+
+/* Callback for tone playback.  Playback latency will be passed
+ * as arg and updated when the first tone
+ */
+static int cras_play_tone(struct cras_client *client,
+                          cras_stream_id_t stream_id,
+                          uint8_t *samples, size_t frames,
+                          const struct timespec *sample_time,
+                          void *arg)
+{
+    snd_pcm_channel_area_t *areas;
+    int chn;
+    size_t sample_bytes;
+
+    sample_bytes = snd_pcm_format_physical_width(format) / 8;
+
+    areas = calloc(channels, sizeof(snd_pcm_channel_area_t));
+    for (chn = 0; chn < channels; chn++) {
+        areas[chn].addr = samples + chn * sample_bytes;
+        areas[chn].first = 0;
+        areas[chn].step = channels *
+                snd_pcm_format_physical_width(format);
+    }
+
+    /* Write zero first at the beginning or noise got captured. */
+    if (cras_put_silent-- > 0 || cras_captured_noise) {
+        memset(samples, 0, sample_bytes * frames);
+    } else {
+        generate_sine(areas, 0, frames, &phase);
+
+        /* Update playback time and latency at first played frame. */
+        if (cras_put_silent == -1) {
+            cras_client_calc_playback_latency(sample_time,
+                              (struct timespec*)arg);
+            cras_play_time = (struct timeval*)malloc(sizeof(*cras_play_time));
+            gettimeofday(cras_play_time, NULL);
+        }
+    }
+    return frames;
+}
+
+static int stream_error(struct cras_client *client,
+                        cras_stream_id_t stream_id,
+                        int err,
+                        void *arg)
+{
+    fprintf(stderr, "Stream error %d\n", err);
+    return 0;
+}
+
+/* Adds stream to cras client.  */
+static int cras_add_stream(struct cras_client *client,
+                           struct cras_stream_params *params,
+                           enum CRAS_STREAM_DIRECTION direction,
+                           struct timespec *user_data)
+{
+    struct cras_audio_format *aud_format;
+    cras_playback_cb_t aud_cb;
+    cras_error_cb_t error_cb;
+    size_t cb_threshold = buffer_frames / 10;
+    size_t min_cb_level = buffer_frames / 10;
+    int rc = 0;
+    cras_stream_id_t stream_id = 0;
+
+    aud_format = cras_audio_format_create(format, rate, channels);
+    if (aud_format == NULL)
+        return -ENOMEM;
+
+    /* Create and start stream */
+    aud_cb = (direction == CRAS_STREAM_OUTPUT)
+            ? cras_play_tone
+            : cras_capture_tone;
+    error_cb = stream_error;
+    params = cras_client_stream_params_create(direction,
+            buffer_frames,
+            cb_threshold,
+            min_cb_level,
+            0,
+            0,
+            user_data,
+            aud_cb,
+            error_cb,
+            aud_format);
+    if (params == NULL)
+        return -ENOMEM;
+
+    rc = cras_client_add_stream(client, &stream_id, params);
+    if (rc < 0) {
+        fprintf(stderr, "Add a stream fail.\n");
+        return rc;
+    }
+    cras_audio_format_destroy(aud_format);
+    return 0;
+}
+
+void cras_test_latency()
+{
+    int rc;
+    struct cras_client *client = NULL;
+    struct cras_stream_params *playback_params = NULL;
+    struct cras_stream_params *capture_params = NULL;
+
+    struct timespec playback_latency;
+    struct timespec capture_latency;
+
+    rc = cras_client_create(&client);
+    if (rc < 0) {
+        fprintf(stderr, "Create client fail.\n");
+        exit(1);
+    }
+    rc = cras_client_connect(client);
+    if (rc < 0) {
+        fprintf(stderr, "Connect to server fail.\n");
+        cras_client_destroy(client);
+        exit(1);
+    }
+
+    cras_client_run_thread(client);
+    rc = cras_add_stream(client,
+                         playback_params,
+                         CRAS_STREAM_OUTPUT,
+                         &playback_latency);
+    if (rc < 0) {
+        fprintf(stderr, "Fail to add playback stream.\n");
+        exit(1);
+    }
+    rc = cras_add_stream(client,
+                         capture_params,
+                         CRAS_STREAM_INPUT,
+                         &capture_latency);
+    if (rc < 0) {
+        fprintf(stderr, "Fail to add capture stream.\n");
+        exit(1);
+    }
+
+    int sleep_count = 10;
+    while (sleep_count-- > 0) {
+        if (cras_captured_noise)
+            break;
+        usleep(300000);
+    }
+
+    if (cras_cap_time && cras_play_time) {
+        unsigned long latency = subtract_timevals(cras_cap_time,
+                                                  cras_play_time);
+        fprintf(stdout, "Measured Latency: %lu uS.\n", latency);
+
+        latency = (playback_latency.tv_sec + capture_latency.tv_sec) * 1000000 +
+                (playback_latency.tv_nsec + capture_latency.tv_nsec) / 1000;
+        fprintf(stdout, "Reported Latency: %lu uS.\n", latency);
+    } else {
+        fprintf(stdout, "Audio not detected.\n");
+    }
+
+    /* Destruct things. */
+    cras_client_stop(client);
+    cras_client_stream_params_destroy(playback_params);
+    cras_client_stream_params_destroy(capture_params);
+    if (cras_play_time)
+        free(cras_play_time);
+    if (cras_cap_time)
+        free(cras_cap_time);
+}
+
+void alsa_test_latency(char *play_dev, char* cap_dev)
+{
+    int err;
+    short *play_buf;
+    short *cap_buf;
+    snd_pcm_t *playback_handle;
+    snd_pcm_t *capture_handle;
+    snd_pcm_uframes_t period_size = buffer_frames / 2;
+
+    unsigned int num_buffers, chn;
+    phase = 0;
+    snd_pcm_sframes_t delay_frames;
+    snd_pcm_sframes_t cap_delay_frames;
+    struct timeval sine_start_tv;
+    snd_pcm_channel_area_t *areas;
+
+    play_buf = calloc(buffer_frames * channels, sizeof(play_buf[0]));
+    cap_buf = calloc(buffer_frames * channels, sizeof(play_buf[0]));
+
+    if ((err = snd_pcm_open(&playback_handle, play_dev,
+                SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+        fprintf(stderr, "cannot open audio device %s (%s)\n",
+                play_dev, snd_strerror(err));
+        exit(1);
+    }
+    config_pcm(playback_handle, rate, channels, format, &buffer_frames,
+           &period_size);
+
+    if ((err = snd_pcm_open(&capture_handle, cap_dev,
+                SND_PCM_STREAM_CAPTURE, 0)) < 0) {
+        fprintf(stderr, "cannot open audio device %s (%s)\n",
+                cap_dev, snd_strerror(err));
+        exit(1);
+    }
+    config_pcm(capture_handle, rate, channels, format, &buffer_frames,
+            &period_size);
+
+    areas = calloc(channels, sizeof(snd_pcm_channel_area_t));
+    for (chn = 0; chn < channels; chn++) {
+        areas[chn].addr = play_buf;
+        areas[chn].first = chn * snd_pcm_format_physical_width(format);
+        areas[chn].step = channels * snd_pcm_format_physical_width(format);
+    }
+
+    /* Begin capture. */
+    if ((err = snd_pcm_start(capture_handle)) < 0) {
+        fprintf(stderr, "cannot start audio interface for use (%s)\n",
+                snd_strerror(err));
+        exit(1);
+    }
+
+    /* Play zeros for a half second. */
+    for (num_buffers = 0; num_buffers < 50; num_buffers++) {
+        if ((err = snd_pcm_writei(playback_handle, play_buf, period_size))
+                != period_size) {
+            fprintf(stderr, "write to audio interface failed (%s)\n",
+                    snd_strerror(err));
+            exit(1);
+        }
+        capture_some(capture_handle, cap_buf, period_size);
+    }
+
+    generate_sine(areas, 0, period_size, &phase);
+    snd_pcm_delay(playback_handle, &delay_frames);
+    gettimeofday(&sine_start_tv, NULL);
+
+    /* Then play a sine wave and look for it on capture.
+     * This will fail for latency > 500mS. */
+    for (num_buffers = 0; num_buffers < 50; num_buffers++) {
+        int num_cap;
+
+        if ((err = snd_pcm_writei(playback_handle, play_buf, period_size))
+                != period_size) {
+            fprintf(stderr, "write to audio interface failed (%s)\n",
+                    snd_strerror(err));
+            exit(1);
+        }
+        snd_pcm_delay(capture_handle, &cap_delay_frames);
+        num_cap = capture_some(capture_handle, cap_buf, period_size);
+
+        if (num_cap > 0 && (num_cap = check_for_noise(cap_buf,
+                                  num_cap,
+                                  channels)) > 0) {
+            struct timeval cap_time;
+            unsigned long latency;
+
+            gettimeofday(&cap_time, NULL);
+
+            fprintf(stderr, "Found audio\n");
+            fprintf(stderr, "Played at %ld %ld, %ld delay\n",
+                    sine_start_tv.tv_sec, sine_start_tv.tv_usec, delay_frames);
+            fprintf(stderr, "Capture at %ld %ld, %ld delay sample %d\n",
+                    cap_time.tv_sec, cap_time.tv_usec,
+                    cap_delay_frames, num_cap);
+
+            latency = subtract_timevals(&cap_time, &sine_start_tv);
+            fprintf(stdout, "Measured Latency: %lu uS\n", latency);
+
+            latency = (delay_frames + cap_delay_frames - num_cap / channels) *
+                    1000000 / rate;
+            fprintf(stdout, "Reported Latency: %lu uS\n", latency);
+            return;
+        }
+        generate_sine(areas, 0, period_size, &phase);
+    }
+
+    fprintf(stdout, "Audio not detected.\n");
+    snd_pcm_close(playback_handle);
+    free(play_buf);
+    free(areas);
+}
+
+int main (int argc, char *argv[])
+{
+    int cras_only = 0;
+    char *play_dev = "default";
+    char *cap_dev = "default";
+
+    int arg;
+    while ((arg = getopt(argc, argv, "b:i:o:n:r:c")) != -1) {
+    switch (arg) {
+        case 'b':
+            buffer_frames = atoi(optarg);
+            break;
+        case 'c':
+            cras_only = 1;
+            break;
+        case 'i':
+            cap_dev = optarg;
+            fprintf(stderr, "Assign cap_dev %s\n", cap_dev);
+            break;
+        case 'n':
+            noise_threshold = atoi(optarg);
+            break;
+        case 'r':
+            rate = atoi(optarg);
+            break;
+        case 'o':
+            play_dev = optarg;
+            fprintf(stderr, "Assign play_dev %s\n", play_dev);
+            break;
+        default:
+            return 1;
+        }
+    }
+
+    if (cras_only)
+        cras_test_latency();
+    else
+        alsa_test_latency(play_dev, cap_dev);
+    exit(0);
+}
diff --git a/client/site_tests/audiovideo_LoopbackLatency/audiovideo_LoopbackLatency.py b/client/site_tests/audiovideo_LoopbackLatency/audiovideo_LoopbackLatency.py
new file mode 100755
index 0000000..ec9d15d
--- /dev/null
+++ b/client/site_tests/audiovideo_LoopbackLatency/audiovideo_LoopbackLatency.py
@@ -0,0 +1,95 @@
+# Copyright (c) 2012 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.
+
+import os
+import logging
+import re
+import subprocess
+import utils
+
+from autotest_lib.client.bin import test, utils
+from autotest_lib.client.common_lib import error
+from autotest_lib.client.cros.audio import audio_helper
+
+
+_DEFAULT_CARD = '0'
+_DEFAULT_VOLUME_LEVEL = 100
+_DEFAULT_CAPTURE_GAIN = 2500
+
+_AUDIO_NOT_FOUND = r'Audio\snot\sdetected'
+_MEASURED_LATENCY = r'Measured\sLatency:\s(\d+)\suS'
+_REPORTED_LATENCY = r'Reported\sLatency:\s(\d+)\suS'
+
+
+class audiovideo_LoopbackLatency(test.test):
+    version = 1
+
+    def initialize(self,
+                   card=_DEFAULT_CARD,
+                   default_volume_level=_DEFAULT_VOLUME_LEVEL,
+                   default_capture_gain=_DEFAULT_CAPTURE_GAIN):
+        '''Setup the deps for the test.
+
+        Args:
+            card: The index of the sound card to use.
+            default_volume_level: The default volume level.
+            defalut_capture_gain: The default capture gain.
+
+        Raises: error.TestError if the deps can't be run
+        '''
+        self._card = card
+
+        self._volume_level = default_volume_level
+        self._capture_gain = default_capture_gain
+
+        self._ah = audio_helper.AudioHelper(self)
+        self._ah.setup_deps(['audioloop'])
+
+        super(audiovideo_LoopbackLatency, self).initialize()
+
+    def run_once(self):
+        self._ah.set_volume_levels(self._volume_level, self._capture_gain)
+        self._loopback_latency_path = os.path.join(self.autodir, 'deps',
+                'audioloop', 'src', 'loopback_latency')
+        noise_threshold =  400
+        measured_latency = None
+        reported_latency = None
+        deviation = None
+        while True:
+            cmdargs = [self._loopback_latency_path, '-n', str(noise_threshold)]
+            proc = subprocess.Popen(cmdargs, stdout=subprocess.PIPE)
+            audio_detected = True
+
+            # Parse loopback_latency output
+            while True:
+                line = proc.stdout.readline()
+                if not line:
+                    break
+                match = re.search(_MEASURED_LATENCY, line, re.I)
+                if match:
+                    measured_latency = int(match.group(1))
+                match = re.search(_REPORTED_LATENCY, line, re.I)
+                if match:
+                    reported_latency = int(match.group(1))
+                if re.search(_AUDIO_NOT_FOUND, line, re.I):
+                    audio_detected = False
+
+            if measured_latency and reported_latency:
+                deviation = (1.0 * abs(measured_latency - reported_latency) /
+                             reported_latency)
+                logging.info('Tested with threshold %d.\nMeasured %d, reported '
+                             '%d uS, deviation %f%%\n' %
+                             (noise_threshold, measured_latency,
+                              reported_latency, deviation * 100))
+            if not audio_detected:
+                logging.info('Audio not detected.')
+                break
+            noise_threshold *= 2
+        if deviation is None:
+            raise error.TestError('No audio detected')
+        elif deviation > .02:
+            raise error.TestError('Latency deviation(%f) too much, measured %d,'
+                                  ' reported %d\n' %
+                                  (deviation, measured_latency,
+                                   reported_latency))
diff --git a/client/site_tests/audiovideo_LoopbackLatency/control b/client/site_tests/audiovideo_LoopbackLatency/control
new file mode 100644
index 0000000..3ad0ed6
--- /dev/null
+++ b/client/site_tests/audiovideo_LoopbackLatency/control
@@ -0,0 +1,22 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = 'The Chromium OS Authors'
+NAME = 'audiovideo_LoopbackLatency'
+PURPOSE = 'Test that audio loopback latency'
+CRITERIA = """
+Check if the audio played to line out is heard by arecord at mic in.
+"""
+TIME='MEDIUM'
+TEST_CATEGORY = 'PLAYBACKCAPTURE'
+TEST_CLASS = "audiovideo"
+TEST_TYPE = 'client'
+
+DOC = """
+NOTE: For this test to pass you need to have the line out looped back to mic-in.
+This can be through a 3.5mm male-to-male cable or a loopback dongle depending on
+your device.
+"""
+
+job.run_test('audiovideo_LoopbackLatency')