| /* 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 <math.h> |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/time.h> |
| |
| #include <alsa/asoundlib.h> |
| |
| #include "cras_client.h" |
| |
| #define CAPTURE_MORE_COUNT 50 |
| #define PLAYBACK_COUNT 50 |
| #define PLAYBACK_SILENT_COUNT 50 |
| #define PLAYBACK_TIMEOUT_COUNT 100 |
| #define TTY_OUTPUT_SIZE 1024 |
| |
| 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_uframes_t period_size = 240; |
| static snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; |
| |
| static struct timeval *cras_play_time = NULL; |
| static struct timeval *cras_cap_time = NULL; |
| static int noise_threshold = 0x4000; |
| static char *tty_output_dev = NULL; |
| static FILE *tty_output = NULL; |
| static const char tty_zeros_block[TTY_OUTPUT_SIZE] = {0}; |
| |
| static int loop; |
| static int cold; |
| static int capture_count; |
| static int playback_count; |
| static snd_pcm_sframes_t playback_delay_frames; |
| static struct timeval sine_start_tv; |
| |
| // Mutex and the variables to protect. |
| static pthread_mutex_t latency_test_mutex; |
| static pthread_cond_t terminate_test; |
| static pthread_cond_t sine_start; |
| static int terminate_playback; |
| static int terminate_capture; |
| static int sine_started = 0; |
| |
| 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); |
| } |
| |
| 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 *cap_delay_frames) |
| { |
| snd_pcm_sframes_t frames = snd_pcm_avail(pcm); |
| int err; |
| |
| if (frames > 0) { |
| frames = frames > len ? len : frames; |
| |
| snd_pcm_delay(pcm, cap_delay_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; |
| } |
| |
| /* Looks for the first sample in buffer whose absolute value exceeds |
| * noise_threshold. Returns the index of found sample in frames, -1 |
| * if not found. */ |
| 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 / channels; |
| 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_index; |
| |
| if (!sine_started || terminate_capture) { |
| return frames; |
| } |
| |
| if ((cap_frames_index = check_for_noise(data, frames, channels)) >= 0) { |
| fprintf(stderr, "Got noise\n"); |
| |
| struct timespec shifted_time = *sample_time; |
| shifted_time.tv_nsec += 1000000000L / 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(&latency_test_mutex); |
| terminate_capture = 1; |
| pthread_cond_signal(&terminate_test); |
| pthread_mutex_unlock(&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; |
| |
| 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); |
| } |
| |
| if (cold) |
| goto play_tone; |
| |
| /* Write zero first when playback_count < PLAYBACK_SILENT_COUNT |
| * or noise got captured. */ |
| if (playback_count < PLAYBACK_SILENT_COUNT) { |
| memset(samples, 0, sample_bytes * frames * channels); |
| } else if (playback_count > PLAYBACK_TIMEOUT_COUNT) { |
| // Timeout, terminate test. |
| pthread_mutex_lock(&latency_test_mutex); |
| terminate_capture = 1; |
| pthread_cond_signal(&terminate_test); |
| pthread_mutex_unlock(&latency_test_mutex); |
| |
| /* for loop mode: to avoid underrun */ |
| memset(samples, 0, sample_bytes * frames * channels); |
| } else { |
| play_tone: |
| generate_sine(areas, 0, frames, &phase); |
| |
| if (!sine_started) { |
| /* Signal that sine tone started playing and update playback time |
| * and latency at first played frame. */ |
| 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); |
| } |
| } |
| } |
| |
| 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 = buffer_frames; |
| size_t min_cb_level = buffer_frames; |
| 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; |
| } |
| |
| static void *alsa_play(void *arg) { |
| snd_pcm_t *handle = (snd_pcm_t *)arg; |
| short *play_buf; |
| snd_pcm_channel_area_t *areas; |
| unsigned int chn, num_buffers; |
| int err; |
| |
| play_buf = calloc(buffer_frames * channels, sizeof(play_buf[0])); |
| 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); |
| } |
| |
| for (num_buffers = 0; num_buffers < PLAYBACK_SILENT_COUNT; num_buffers++) { |
| if ((err = snd_pcm_writei(handle, play_buf, period_size)) |
| != period_size) { |
| fprintf(stderr, "write to audio interface failed (%s)\n", |
| snd_strerror(err)); |
| exit(1); |
| } |
| } |
| |
| generate_sine(areas, 0, period_size, &phase); |
| snd_pcm_delay(handle, &playback_delay_frames); |
| gettimeofday(&sine_start_tv, NULL); |
| |
| num_buffers = 0; |
| int avail_frames; |
| |
| /* Play a sine wave and look for it on capture thread. |
| * This will fail for latency > 500mS. */ |
| while (!terminate_playback && num_buffers < PLAYBACK_COUNT) { |
| avail_frames = snd_pcm_avail(handle); |
| if (avail_frames >= period_size) { |
| pthread_mutex_lock(&latency_test_mutex); |
| if (!sine_started) { |
| sine_started = 1; |
| pthread_cond_signal(&sine_start); |
| } |
| pthread_mutex_unlock(&latency_test_mutex); |
| if ((err = snd_pcm_writei(handle, play_buf, period_size)) |
| != period_size) { |
| fprintf(stderr, "write to audio interface failed (%s)\n", |
| snd_strerror(err)); |
| } |
| num_buffers++; |
| } |
| } |
| terminate_playback = 1; |
| |
| if (num_buffers == PLAYBACK_COUNT) |
| fprintf(stdout, "Audio not detected.\n"); |
| |
| free(play_buf); |
| free(areas); |
| return 0; |
| } |
| |
| static void *alsa_capture(void *arg) { |
| int err; |
| short *cap_buf; |
| snd_pcm_t *capture_handle = (snd_pcm_t *)arg; |
| snd_pcm_sframes_t cap_delay_frames; |
| int num_cap, noise_delay_frames; |
| |
| |
| cap_buf = calloc(buffer_frames * channels, sizeof(cap_buf[0])); |
| |
| pthread_mutex_lock(&latency_test_mutex); |
| while (!sine_started) { |
| pthread_cond_wait(&sine_start, &latency_test_mutex); |
| } |
| pthread_mutex_unlock(&latency_test_mutex); |
| |
| /* 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); |
| } |
| |
| while (!terminate_capture) { |
| num_cap = capture_some(capture_handle, cap_buf, |
| buffer_frames, &cap_delay_frames); |
| |
| if (num_cap > 0 && (noise_delay_frames = check_for_noise(cap_buf, |
| num_cap, channels)) >= 0) { |
| struct timeval cap_time; |
| unsigned long latency_us; |
| gettimeofday(&cap_time, NULL); |
| |
| fprintf(stderr, "Found audio\n"); |
| fprintf(stderr, "Played at %llu %llu, %ld delay\n", |
| (unsigned long long)sine_start_tv.tv_sec, |
| (unsigned long long)sine_start_tv.tv_usec, |
| playback_delay_frames); |
| fprintf(stderr, "Capture at %llu %llu, %ld delay sample %d\n", |
| (unsigned long long)cap_time.tv_sec, |
| (unsigned long long)cap_time.tv_usec, |
| cap_delay_frames, noise_delay_frames); |
| |
| latency_us = subtract_timevals(&cap_time, &sine_start_tv); |
| fprintf(stdout, "Measured Latency: %lu uS\n", latency_us); |
| |
| latency_us = (playback_delay_frames + cap_delay_frames - |
| noise_delay_frames) * 1000000 / rate; |
| fprintf(stdout, "Reported Latency: %lu uS\n", latency_us); |
| |
| // Noise captured, terminate both threads. |
| terminate_playback = 1; |
| terminate_capture = 1; |
| } else { |
| // Capture some more buffers after playback thread has terminated. |
| if (terminate_playback && capture_count++ < CAPTURE_MORE_COUNT) |
| terminate_capture = 1; |
| } |
| } |
| |
| free(cap_buf); |
| 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(&latency_test_mutex, NULL); |
| pthread_cond_init(&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(&latency_test_mutex); |
| while (!terminate_capture) { |
| pthread_cond_wait(&terminate_test, &latency_test_mutex); |
| } |
| pthread_mutex_unlock(&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 (--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; |
| playback_count = 0; |
| |
| pthread_mutex_lock(&latency_test_mutex); |
| terminate_capture = 0; |
| sine_started = 0; |
| pthread_mutex_unlock(&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); |
| } |
| |
| void alsa_test_latency(char *play_dev, char *cap_dev) |
| { |
| int err; |
| snd_pcm_t *playback_handle; |
| snd_pcm_t *capture_handle; |
| |
| pthread_t capture_thread; |
| pthread_t playback_thread; |
| |
| 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); |
| |
| pthread_mutex_init(&latency_test_mutex, NULL); |
| pthread_cond_init(&sine_start, NULL); |
| |
| pthread_create(&playback_thread, NULL, alsa_play, playback_handle); |
| pthread_create(&capture_thread, NULL, alsa_capture, capture_handle); |
| |
| pthread_join(capture_thread, NULL); |
| pthread_join(playback_thread, NULL); |
| |
| snd_pcm_close(playback_handle); |
| snd_pcm_close(capture_handle); |
| } |
| |
| int main (int argc, char *argv[]) |
| { |
| int cras_only = 0; |
| char *play_dev = NULL; |
| char *cap_dev = NULL; |
| |
| int arg; |
| while ((arg = getopt(argc, argv, "b:i:o:n:r:p:ct:l: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; |
| case 'p': |
| period_size = atoi(optarg); |
| break; |
| case 't': |
| tty_output_dev = optarg; |
| break; |
| case 'l': |
| loop = atoi(optarg); |
| break; |
| case 'C': |
| cold = 1; |
| break; |
| default: |
| return 1; |
| } |
| } |
| |
| if (loop && cold) { |
| fprintf(stderr, "Cold and loop are exclusive.\n"); |
| exit(1); |
| } |
| |
| if (cras_only) |
| cras_test_latency(); |
| else { |
| if (play_dev == NULL || cap_dev == NULL) { |
| fprintf(stderr, "Input/output devices must be set in Alsa mode.\n"); |
| exit(1); |
| } |
| alsa_test_latency(play_dev, cap_dev); |
| } |
| exit(0); |
| } |