cras_api_test: Stress test for input stream.
The stress test could be used to re-produce a bug that cras cannot read
from input device.
BUG=b:29142540
TEST=Run 'cras_api_test -n 100' on cyan.
Change-Id: I396a866f851ec5e666d3fe0f7d5b1436daa9f459
Reviewed-on: https://chromium-review.googlesource.com/358750
Commit-Ready: Chinyue Chen <chinyue@chromium.org>
Tested-by: Chinyue Chen <chinyue@chromium.org>
Reviewed-by: Cheng-Yi Chiang <cychiang@chromium.org>
diff --git a/Makefile b/Makefile
index 31a1be6..0b9f6cf 100644
--- a/Makefile
+++ b/Makefile
@@ -58,6 +58,16 @@
clean: CC_BINARY(loopback_latency)
all: CC_BINARY(loopback_latency)
+CC_BINARY(cras_api_test): $(filter \
+ cras_api_test.o \
+ ,$(C_OBJECTS))
+CC_BINARY(cras_api_test): \
+ CFLAGS += $(ALSA_CFLAGS) $(CRAS_CFLAGS)
+CC_BINARY(cras_api_test): \
+ LDLIBS += $(ALSA_LIBS) $(CRAS_LIBS)
+clean: CC_BINARY(cras_api_test)
+all: CC_BINARY(cras_api_test)
+
CC_BINARY(alsa_api_test): $(filter \
alsa_api_test.o \
,$(C_OBJECTS))
diff --git a/cras_api_test.c b/cras_api_test.c
new file mode 100644
index 0000000..5870597
--- /dev/null
+++ b/cras_api_test.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2016 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.
+ */
+
+#include <pthread.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "cras_client.h"
+
+#define CAPTURE_PERIOD_SIZE 256
+#define CAPTURE_PERIOD_COUNT 4
+
+int num_retries = 3;
+int wait_sec = 10;
+
+struct stream_in {
+ uint32_t sample_rate;
+ uint32_t channels;
+ uint32_t period_size;
+ struct cras_audio_format* cras_format;
+ struct cras_stream_params* cras_params;
+ cras_stream_id_t stream_id;
+
+ uint32_t frames_to_capture;
+ uint32_t frames_captured;
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+};
+
+static uint32_t get_input_period_size(uint32_t sample_rate)
+{
+ /*
+ * The supported capture sample rates range from 8000 to 48000.
+ * We need to use different buffer size when creating CRAS stream
+ * so that appropriate latency is maintained.
+ */
+ if (sample_rate <= 12000)
+ return (CAPTURE_PERIOD_SIZE) / 4;
+ else if (sample_rate <= 24000)
+ return (CAPTURE_PERIOD_SIZE) / 2;
+ else
+ return CAPTURE_PERIOD_SIZE;
+}
+
+void cras_server_error_cb(struct cras_client *client, void *user_arg);
+
+int cras_open(struct cras_client **client)
+{
+ int rc;
+
+ rc = cras_client_create(client);
+ if (rc < 0) {
+ fprintf(stderr, "%s: Failed to create CRAS client.\n", __func__);
+ return rc;
+ }
+
+ rc = cras_client_connect_timeout(*client, 3000);
+ if (rc) {
+ fprintf(stderr, "%s: Failed to connect to CRAS server, rc %d.\n",
+ __func__, rc);
+ goto fail;
+ }
+
+ rc = cras_client_run_thread(*client);
+ if (rc) {
+ fprintf(stderr, "%s: Failed to start CRAS client.\n", __func__);
+ goto fail;
+ }
+
+ rc = cras_client_connected_wait(*client);
+ if (rc) {
+ fprintf(stderr, "%s: Failed to wait for connected.\n", __func__);
+ goto fail;
+ }
+
+ cras_client_set_server_error_cb(*client, cras_server_error_cb, NULL);
+
+ return 0;
+
+fail:
+ cras_client_destroy(*client);
+ return rc;
+}
+
+int cras_close(struct cras_client *client)
+{
+ cras_client_stop(client);
+ cras_client_destroy(client);
+ return 0;
+}
+
+void cras_server_error_cb(struct cras_client *client, void *user_arg)
+{
+ fprintf(stderr, "Server error!\n");
+ cras_close(client);
+}
+
+static int in_read_cb(struct cras_client *client,
+ cras_stream_id_t stream_id,
+ uint8_t *captured_samples,
+ uint8_t *playback_samples,
+ unsigned int frames,
+ const struct timespec *captured_time,
+ const struct timespec *playback_time,
+ void *user_arg)
+{
+ struct stream_in *in = (struct stream_in *)user_arg;
+
+ pthread_mutex_lock(&in->lock);
+
+ in->frames_captured += frames;
+ if (in->frames_captured >= in->frames_to_capture)
+ pthread_cond_signal(&in->cond);
+
+ pthread_mutex_unlock(&in->lock);
+
+ return frames;
+}
+
+static int in_err_cb(struct cras_client *client,
+ cras_stream_id_t stream_id,
+ int error,
+ void *user_arg)
+{
+ fprintf(stderr, "%s: enter\n", __func__);
+ return 0;
+}
+
+struct stream_in *open_input_stream(struct cras_client *client,
+ uint32_t sample_rate, uint32_t channels)
+{
+ struct stream_in *in;
+
+ printf("%s: sample_rate %d channels %d\n", __func__, sample_rate, channels);
+
+ in = (struct stream_in *)calloc(1, sizeof(struct stream_in));
+
+ in->sample_rate = sample_rate;
+ in->channels = channels;
+ in->period_size = get_input_period_size(in->sample_rate);
+
+ in->cras_format = cras_audio_format_create(
+ SND_PCM_FORMAT_S16_LE,
+ in->sample_rate,
+ in->channels);
+ if (!in->cras_format) {
+ fprintf(stderr, "%s: Failed to create CRAS audio format.\n", __func__);
+ goto cleanup;
+ }
+
+ in->cras_params = cras_client_unified_params_create(
+ CRAS_STREAM_INPUT,
+ in->period_size,
+ CRAS_STREAM_TYPE_DEFAULT,
+ 0,
+ (void *)in,
+ in_read_cb,
+ in_err_cb,
+ in->cras_format);
+ if (!in->cras_params) {
+ fprintf(stderr, "%s: Failed to create stream params.\n", __func__);
+ goto cleanup;
+ }
+
+ printf("%s: stream created.\n", __func__);
+ return in;
+
+cleanup:
+ if (in->cras_format)
+ cras_audio_format_destroy(in->cras_format);
+ if (in->cras_params)
+ cras_client_stream_params_destroy(in->cras_params);
+ free(in);
+ return NULL;
+}
+
+int start_input_stream(struct cras_client *client, struct stream_in *in)
+{
+ return cras_client_add_stream(client, &in->stream_id, in->cras_params);
+}
+
+void close_input_stream(struct cras_client *client, struct stream_in *in)
+{
+ if (in->stream_id)
+ cras_client_rm_stream(client, in->stream_id);
+ cras_audio_format_destroy(in->cras_format);
+ cras_client_stream_params_destroy(in->cras_params);
+ free(in);
+}
+
+int cras_test_capture(struct cras_client *client, uint32_t sample_rate,
+ uint32_t channels, uint32_t segment_ms, uint32_t segments)
+{
+ struct stream_in *in;
+ int i, ret = 0, fail_count = 0;
+ pthread_condattr_t attr;
+ struct timespec abs_ts, wait_ts;
+
+ in = open_input_stream(client, sample_rate, channels);
+ if (!in) {
+ fprintf(stderr, "%s: Failed to open input stream.\n", __func__);
+ return -1;
+ }
+
+ in->frames_to_capture = in->sample_rate * segment_ms / 1000;
+
+ pthread_mutex_init(&in->lock, (const pthread_mutexattr_t *)NULL);
+ pthread_condattr_init(&attr);
+ pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
+ pthread_cond_init(&in->cond, &attr);
+
+ pthread_mutex_lock(&in->lock);
+
+ ret = start_input_stream(client, in);
+ if (ret) {
+ pthread_mutex_unlock(&in->lock);
+ fprintf(stderr, "%s: Failed to start input stream.\n", __func__);
+ goto cleanup;
+ }
+
+ for (i = 0; i < segments; ++i) {
+ in->frames_captured = 0;
+
+ // Wait for captured data.
+ if (wait_sec > 0) {
+ clock_gettime(CLOCK_MONOTONIC, &abs_ts);
+ wait_ts.tv_sec = wait_sec;
+ wait_ts.tv_nsec = 0;
+ add_timespecs(&abs_ts, &wait_ts);
+ ret = pthread_cond_timedwait(&in->cond, &in->lock, &abs_ts);
+ } else {
+ ret = pthread_cond_wait(&in->cond, &in->lock);
+ }
+ if (ret) {
+ if (ret == ETIMEDOUT) {
+ fprintf(stderr, "%s: Failed to receive captured data.\n",
+ __func__);
+ } else {
+ fprintf(stderr, "%s: Failed to wait for data.\n", __func__);
+ }
+ if (++fail_count >= num_retries)
+ break;
+ else
+ continue;
+ }
+
+ printf("%s: Captured %d frames for segment %d.\n", __func__,
+ in->frames_captured, i);
+ }
+
+ pthread_mutex_unlock(&in->lock);
+
+cleanup:
+ close_input_stream(client, in);
+ return ret;
+}
+
+int cras_stress_capture(struct cras_client *client)
+{
+ uint32_t test_sample_rate[] = {
+ 12345,
+ 44100,
+ 48000,
+ 96000,
+ 192000,
+ };
+ uint32_t test_channels[] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ };
+ int i, j;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(test_sample_rate); ++i) {
+ for (j = 0; j < ARRAY_SIZE(test_channels); ++j) {
+ ret = cras_test_capture(client, test_sample_rate[i],
+ test_channels[j], 20, 10);
+ if (ret) {
+ fprintf(stderr,
+ "%s: Failed to capture sample_rate %d channels %d.\n",
+ __func__, test_sample_rate[i], test_channels[j]);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int main (int argc, char *argv[])
+{
+ struct cras_client *client;
+ int c, i, rc;
+ int num_tests = 1;
+ struct option long_opt[] =
+ {
+ { "help", no_argument, NULL, 'h' },
+ { "num_tests", required_argument, NULL, 'n' },
+ { "num_retries", required_argument, NULL, 'r' },
+ { "wait_sec", required_argument, NULL, 'w' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ while (1) {
+ c = getopt_long(argc, argv, "hn:r:w:", long_opt, NULL);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'n':
+ num_tests = atoi(optarg);
+ break;
+
+ case 'r':
+ num_retries = atoi(optarg);
+ break;
+
+ case 'w':
+ wait_sec = atoi(optarg);
+ break;
+
+ case 'h':
+ printf("Usage: %s [OPTIONS]\n", argv[0]);
+ printf(" --num_tests <num> Number of test runs, "
+ "default: %d\n", num_tests);
+ printf(" --num_retries <num> Number of retries, "
+ "default: %d\n", num_retries);
+ printf(" --wait_sec <num> Wait seconds for captured data, "
+ "default: %d, use 0 to wait infinitely\n", wait_sec);
+ printf(" -h, --help Print this help and exit\n");
+ printf("\n");
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ rc = cras_open(&client);
+ if (rc) {
+ fprintf(stderr, "%s: Failed to open CRAS, rc %d.\n", __func__, rc);
+ exit(EXIT_FAILURE);
+ }
+
+ // Run tests.
+ for (i = 0; i < num_tests; ++i) {
+ printf("%s: ============\n", __func__);
+ printf("%s: TEST PASS %d\n", __func__, i);
+ printf("%s: ============\n", __func__);
+ rc = cras_stress_capture(client);
+ if (rc) {
+ fprintf(stderr, "%s: Failed in test pass %d.\n", __func__, i);
+ break;
+ }
+ }
+
+ if (client)
+ cras_close(client);
+
+ exit(EXIT_SUCCESS);
+}