blob: 07076921ce3519b3b34e8d3c8226b0b1f6f66e13 [file]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <alsa/asoundlib.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include "args.h"
#include "common.h"
#include "cras_client.h"
#define TTY_OUTPUT_SIZE 1024
char* tty_output_dev = NULL;
static FILE* tty_output = NULL;
static const char tty_zeros_block[TTY_OUTPUT_SIZE] = {0};
static struct timeval* cras_play_time = NULL;
static struct timeval* cras_cap_time = NULL;
static pthread_cond_t terminate_test;
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(g_format) == 16);
short* data = (short*)samples;
int cap_frames_index;
if (!g_sine_started || g_terminate_capture) {
return frames;
}
if ((cap_frames_index = check_for_noise(data, frames, g_channels)) >= 0) {
fprintf(stderr, "Got noise\n");
struct timespec shifted_time = *sample_time;
shifted_time.tv_nsec += 1000000000L / g_rate * cap_frames_index;
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);
// Terminate the test since noise got captured.
pthread_mutex_lock(&g_latency_test_mutex);
g_terminate_capture = 1;
pthread_cond_signal(&terminate_test);
pthread_mutex_unlock(&g_latency_test_mutex);
}
return frames;
}
/* Callback for tone playback. Playback latency will be passed
* as arg and updated at the first sine tone right after silent.
*/
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;
if (g_playback_file != NULL) {
fprintf(stderr, "Using playback file is not supproted for CRAS yet\n");
exit(EXIT_FAILURE);
}
sample_bytes = snd_pcm_format_physical_width(g_format) / 8;
areas = calloc(g_channels, sizeof(snd_pcm_channel_area_t));
for (chn = 0; chn < g_channels; chn++) {
areas[chn].addr = samples + chn * sample_bytes;
areas[chn].first = 0;
areas[chn].step = g_channels * snd_pcm_format_physical_width(g_format);
}
if (g_cold)
goto play_tone;
/* Write zero first when playback_count < PLAYBACK_SILENT_COUNT
* or noise got captured. */
if (g_playback_count < PLAYBACK_SILENT_COUNT) {
memset(samples, 0, sample_bytes * frames * g_channels);
} else if (g_playback_count > PLAYBACK_TIMEOUT_COUNT) {
// Timeout, terminate test.
pthread_mutex_lock(&g_latency_test_mutex);
g_terminate_capture = 1;
pthread_cond_signal(&terminate_test);
pthread_mutex_unlock(&g_latency_test_mutex);
/* for loop mode: to avoid underrun */
memset(samples, 0, sample_bytes * frames * g_channels);
} else {
play_tone:
generate_sine(areas, 0, frames, &g_phase);
if (!g_sine_started) {
/* Signal that sine tone started playing and update playback time
* and latency at first played frame. */
g_sine_started = 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);
if (tty_output) {
fwrite(tty_zeros_block, sizeof(char), TTY_OUTPUT_SIZE, tty_output);
fflush(tty_output);
}
}
}
g_playback_count++;
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 = g_buffer_frames;
size_t min_cb_level = g_buffer_frames;
int rc = 0;
cras_stream_id_t stream_id = 0;
aud_format = cras_audio_format_create(g_format, g_rate, g_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, g_buffer_frames, cb_threshold, min_cb_level, 0, 0, user_data,
aud_cb, error_cb, aud_format);
if (params == NULL)
return -ENOMEM;
if (direction == CRAS_STREAM_INPUT &&
g_pin_capture_device != PIN_DEVICE_UNSET) {
rc = cras_client_add_pinned_stream(client, g_pin_capture_device, &stream_id,
params);
} else {
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);
}
if (tty_output_dev) {
tty_output = fopen(tty_output_dev, "w");
if (!tty_output)
fprintf(stderr, "Failed to open TTY output device: %s", tty_output_dev);
else
fprintf(stdout, "Opened %s for UART signal\n", tty_output_dev);
}
pthread_mutex_init(&g_latency_test_mutex, NULL);
pthread_cond_init(&g_sine_start, NULL);
pthread_cond_init(&terminate_test, NULL);
cras_client_run_thread(client);
// Sleep 500ms to skip input cold start time.
fprintf(stderr, "Create capture stream and wait for 500ms.\n");
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);
}
struct timespec delay = {.tv_sec = 0, .tv_nsec = 500000000};
nanosleep(&delay, NULL);
fprintf(stderr, "Create playback stream.\n");
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);
}
again:
pthread_mutex_lock(&g_latency_test_mutex);
while (!g_terminate_capture) {
pthread_cond_wait(&terminate_test, &g_latency_test_mutex);
}
pthread_mutex_unlock(&g_latency_test_mutex);
if (cras_cap_time && cras_play_time) {
unsigned long playback_latency_us, capture_latency_us;
unsigned long latency = subtract_timevals(cras_cap_time, cras_play_time);
fprintf(stdout, "Measured Latency: %lu uS.\n", latency);
playback_latency_us =
(playback_latency.tv_sec * 1000000) + (playback_latency.tv_nsec / 1000);
capture_latency_us =
(capture_latency.tv_sec * 1000000) + (capture_latency.tv_nsec / 1000);
fprintf(stdout,
"Reported Latency: %lu uS.\n"
"Reported Output Latency: %lu uS.\n"
"Reported Input Latency: %lu uS.\n",
playback_latency_us + capture_latency_us, playback_latency_us,
capture_latency_us);
fflush(stdout);
} else {
fprintf(stdout, "Audio not detected.\n");
}
if (--g_loop > 0) {
if (cras_play_time)
free(cras_play_time);
if (cras_cap_time)
free(cras_cap_time);
usleep(50000);
cras_play_time = cras_cap_time = NULL;
g_playback_count = 0;
pthread_mutex_lock(&g_latency_test_mutex);
g_terminate_capture = 0;
g_sine_started = 0;
pthread_mutex_unlock(&g_latency_test_mutex);
goto again;
}
/* Destruct things. */
cras_client_stop(client);
cras_client_stream_params_destroy(playback_params);
cras_client_stream_params_destroy(capture_params);
if (tty_output)
fclose(tty_output);
if (cras_play_time)
free(cras_play_time);
if (cras_cap_time)
free(cras_cap_time);
}