alsa-utils/bat/latencytest.c

242 lines
6.1 KiB
C
Raw Permalink Normal View History

/*
* Copyright (C) 2013-2015 Intel Corporation
*
* 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.
*
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "common.h"
#include "bat-signal.h"
#include "gettext.h"
/* How one measurement step works:
- Listen and measure the average loudness of the environment for 1 second.
- Create a threshold value 16 decibels higher than the average loudness.
- Begin playing a ~1000 Hz sine wave and start counting the samples elapsed.
- Stop counting and playing if the input's loudness is higher than the
threshold, as the output wave is probably coming back.
- Calculate the round trip audio latency value in milliseconds. */
static float sumaudio(struct bat *bat, short int *buffer, int frames)
{
float sum = 0;
int n = 0;
while (frames) {
frames--;
for (n = 0; n < bat->channels; n++) {
sum += abs(buffer[0]);
buffer++;
}
}
sum = sum / bat->channels;
return sum;
}
static void play_and_listen(struct bat *bat, void *buffer, int frames)
{
int averageinput;
int n = 0;
float sum = 0;
float max = 0;
float min = 100000.0f;
short int *input;
int num = bat->latency.number;
averageinput = (int) (sumaudio(bat, buffer, frames) / frames);
/* The signal is above threshold
So our sine wave comes back on the input */
if (averageinput > bat->latency.threshold) {
input = buffer;
/* Check the location when it became loud enough */
while (n < frames) {
if (*input++ > bat->latency.threshold)
break;
*input += bat->channels;
n++;
}
/* Now we get the total round trip latency*/
bat->latency.samples += n;
/* Expect at least 1 buffer of round trip latency. */
if (bat->latency.samples > frames) {
bat->latency.result[num - 1] =
(float) bat->latency.samples * 1000 / bat->rate;
fprintf(bat->log,
_("Test%d, round trip latency %dms\n"),
num,
(int) bat->latency.result[num - 1]);
for (n = 0; n < num; n++) {
if (bat->latency.result[n] > max)
max = bat->latency.result[n];
if (bat->latency.result[n] < min)
min = bat->latency.result[n];
sum += bat->latency.result[n];
}
/* The maximum is higher than the minimum's double */
if (max / min > 2.0f) {
bat->latency.state =
LATENCY_STATE_COMPLETE_FAILURE;
bat->latency.is_capturing = false;
return;
/* Final results */
} else if (num == LATENCY_TEST_NUMBER) {
bat->latency.final_result =
(int) (sum / LATENCY_TEST_NUMBER);
fprintf(bat->log,
_("Final round trip latency: %dms\n"),
bat->latency.final_result);
bat->latency.state =
LATENCY_STATE_COMPLETE_SUCCESS;
bat->latency.is_capturing = false;
return;
/* Next step */
} else
bat->latency.state = LATENCY_STATE_WAITING;
bat->latency.number++;
} else
/* Happens when an early noise comes in */
bat->latency.state = LATENCY_STATE_WAITING;
} else {
/* Still listening */
bat->latency.samples += frames;
/* Do not listen to more than a second
Maybe too much background noise */
if (bat->latency.samples > bat->rate) {
bat->latency.error++;
if (bat->latency.error > LATENCY_TEST_NUMBER) {
fprintf(bat->err,
_("Could not detect signal."));
fprintf(bat->err,
_("Too much background noise?\n"));
bat->latency.state =
LATENCY_STATE_COMPLETE_FAILURE;
bat->latency.is_capturing = false;
return;
}
/* let's start over */
bat->latency.state = LATENCY_STATE_WAITING;
}
}
return;
}
static void calculate_threshold(struct bat *bat)
{
float average;
float reference;
/* Calculate the average loudness of the environment and create
a threshold value 16 decibels higher than the average loudness */
average = bat->latency.sum / bat->latency.samples / 32767.0f;
reference = 20.0f * log10f(average) + 16.0f;
bat->latency.threshold = (int) (powf(10.0f, reference / 20.0f)
* 32767.0f);
}
void roundtrip_latency_init(struct bat *bat)
{
bat->latency.number = 1;
bat->latency.state = LATENCY_STATE_MEASURE_FOR_1_SECOND;
bat->latency.final_result = 0;
bat->latency.samples = 0;
bat->latency.sum = 0;
bat->latency.threshold = 0;
bat->latency.is_capturing = false;
bat->latency.is_playing = false;
bat->latency.error = 0;
bat->latency.xrun_error = false;
bat->frames = LATENCY_TEST_TIME_LIMIT * bat->rate;
bat->periods_played = 0;
}
int handleinput(struct bat *bat, void *buffer, int frames)
{
switch (bat->latency.state) {
/* Measuring average loudness for 1 second */
case LATENCY_STATE_MEASURE_FOR_1_SECOND:
bat->latency.sum += sumaudio(bat, buffer, frames);
bat->latency.samples += frames;
/* 1 second elapsed */
if (bat->latency.samples >= bat->rate) {
calculate_threshold(bat);
bat->latency.state = LATENCY_STATE_PLAY_AND_LISTEN;
bat->latency.samples = 0;
bat->latency.sum = 0;
}
break;
/* Playing sine wave and listening if it comes back */
case LATENCY_STATE_PLAY_AND_LISTEN:
play_and_listen(bat, buffer, frames);
break;
/* Waiting 1 second */
case LATENCY_STATE_WAITING:
bat->latency.samples += frames;
if (bat->latency.samples > bat->rate) {
/* 1 second elapsed, start over */
bat->latency.samples = 0;
bat->latency.state = LATENCY_STATE_MEASURE_FOR_1_SECOND;
}
break;
default:
return 0;
}
return 0;
}
int handleoutput(struct bat *bat, void *buffer, int bytes, int frames)
{
int err = 0;
/* If capture completed, terminate the playback */
if (bat->periods_played * frames > 2 * bat->rate
&& bat->latency.is_capturing == false)
return bat->latency.state;
if (bat->latency.state == LATENCY_STATE_PLAY_AND_LISTEN)
err = generate_sine_wave(bat, frames, buffer);
else
/* Output silence */
memset(buffer, 0, bytes);
return err;
}