/* 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.
 */

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/param.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "cras_client.h"
#include "cras_types.h"
#include "cras_util.h"
#include "cras_version.h"

#define NOT_ASSIGNED (0)
#define PLAYBACK_BUFFERED_TIME_IN_US (5000)

#define BUF_SIZE 32768

static const size_t MAX_IODEVS = 10; /* Max devices to print out. */
static const size_t MAX_IONODES = 20; /* Max ionodes to print out. */
static const size_t MAX_ATTACHED_CLIENTS = 10; /* Max clients to print out. */

static int pipefd[2];
static struct timespec last_latency;
static int show_latency;
static float last_rms_sqr_sum;
static int last_rms_size;
static float total_rms_sqr_sum;
static int total_rms_size;
static int show_rms;
static int show_total_rms;
static int keep_looping = 1;
static int exit_after_done_playing = 1;
static size_t duration_frames;
static int pause_client = 0;
static int pause_a_reply = 0;
static int pause_in_playback_reply = 1000;

static char *channel_layout = NULL;
static int pin_device_id;

static int play_short_sound = 0;
static int play_short_sound_periods = 0;
static int play_short_sound_periods_left = 0;

/* Conditional so the client thread can signal that main should exit. */
static pthread_mutex_t done_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t done_cond = PTHREAD_COND_INITIALIZER;

struct cras_audio_format *aud_format;

static int terminate_stream_loop()
{
	keep_looping = 0;
	return write(pipefd[1], "1", 1);
}

static size_t get_block_size(uint64_t buffer_time_in_us, size_t rate)
{
	return (size_t)(buffer_time_in_us * rate / 1000000);
}

static void check_stream_terminate(size_t frames)
{
	if (duration_frames) {
		if (duration_frames <= frames)
			terminate_stream_loop();
		else
			duration_frames -= frames;
	}
}

/* Compute square sum of samples (for calculation of RMS value). */
float compute_sqr_sum_16(const int16_t *samples, int size)
{
	unsigned i;
	float sqr_sum = 0;

	for (i = 0; i < size; i++)
		sqr_sum += samples[i] * samples[i];

	return sqr_sum;
}

/* Update the RMS values with the given samples. */
int update_rms(const uint8_t *samples, int size)
{
	switch (aud_format->format) {
	case SND_PCM_FORMAT_S16_LE: {
		last_rms_sqr_sum = compute_sqr_sum_16((int16_t *)samples, size / 2);
		last_rms_size = size / 2;
		break;
	}
	default:
		return -EINVAL;
	}

	total_rms_sqr_sum += last_rms_sqr_sum;
	total_rms_size += last_rms_size;

	return 0;
}

/* Run from callback thread. */
static int got_samples(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)
{
	int *fd = (int *)user_arg;
	int ret;
	int write_size;
	int frame_bytes;

	while (pause_client)
		usleep(10000);

	cras_client_calc_capture_latency(captured_time, &last_latency);

	frame_bytes = cras_client_format_bytes_per_frame(aud_format);
	write_size = frames * frame_bytes;

	/* Update RMS values with all available frames. */
	if (keep_looping) {
		update_rms(captured_samples,
			   MIN(write_size, duration_frames * frame_bytes));
	}

	check_stream_terminate(frames);

	ret = write(*fd, captured_samples, write_size);
	if (ret != write_size)
		printf("Error writing file\n");
	return frames;
}

/* Run from callback thread. */
static int got_hotword(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)
{
	printf("got hotword %u frames\n", frames);

	return frames;
}

/* Run from callback thread. */
static int put_samples(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)
{
	uint32_t frame_bytes = cras_client_format_bytes_per_frame(aud_format);
	int fd = *(int *)user_arg;
	uint8_t buff[BUF_SIZE];
	int nread;

	while (pause_client)
		usleep(10000);

	if (pause_a_reply) {
		usleep(pause_in_playback_reply);
		pause_a_reply = 0;
	}

	check_stream_terminate(frames);

	cras_client_calc_playback_latency(playback_time, &last_latency);

	if (play_short_sound) {
		if (play_short_sound_periods_left)
			/* Play a period from file. */
			play_short_sound_periods_left--;
		else {
			/* Fill zeros to play silence. */
			memset(playback_samples, 0,
			       MIN(frames * frame_bytes, BUF_SIZE));
			return frames;
		}
	}

	nread = read(fd, buff, MIN(frames * frame_bytes, BUF_SIZE));
	if (nread <= 0) {
		if (exit_after_done_playing)
			terminate_stream_loop();
		return nread;
	}

	memcpy(playback_samples, buff, nread);
	return nread / frame_bytes;
}

/* Run from callback thread. */
static int put_stdin_samples(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)
{
	int rc = 0;
	uint32_t frame_bytes = cras_client_format_bytes_per_frame(aud_format);

	rc = read(0, playback_samples, frames * frame_bytes);
	if (rc <= 0) {
		terminate_stream_loop();
		return -1;
	}

	return rc / frame_bytes;
}

static int stream_error(struct cras_client *client,
			cras_stream_id_t stream_id,
			int err,
			void *arg)
{
	printf("Stream error %d\n", err);
	terminate_stream_loop();
	return 0;
}

static void print_last_latency()
{
	if (last_latency.tv_sec > 0 || last_latency.tv_nsec > 0)
		printf("%u.%09u\n", (unsigned)last_latency.tv_sec,
		       (unsigned)last_latency.tv_nsec);
	else {
		printf("-%lld.%09lld\n", (long long)-last_latency.tv_sec,
		       (long long)-last_latency.tv_nsec);
	}
}

static void print_last_rms()
{
	if (last_rms_size != 0)
		printf("%.9f\n", sqrt(last_rms_sqr_sum / last_rms_size));
}

static void print_total_rms()
{
	if (total_rms_size != 0)
		printf("%.9f\n", sqrt(total_rms_sqr_sum / total_rms_size));
}

static void print_dev_info(const struct cras_iodev_info *devs, int num_devs)
{
	unsigned i;

	printf("\tID\tName\n");
	for (i = 0; i < num_devs; i++)
		printf("\t%u\t%s\n", devs[i].idx, devs[i].name);
}

static void print_node_info(const struct cras_ionode_info *nodes, int num_nodes,
			    int is_input)
{
	unsigned i;

	printf("\tStable Id\t ID\t%4s   Plugged\tL/R swapped\t      "
	       "Time Hotword\tType\t\t Name\n", is_input ? "Gain" : " Vol");
	for (i = 0; i < num_nodes; i++)
		printf("\t(%08x)\t%u:%u\t%5g  %7s\t%14s\t%10ld %-7s\t%-16s%c%s\n",
		       nodes[i].stable_id,
		       nodes[i].iodev_idx,
		       nodes[i].ionode_idx,
		       is_input ? nodes[i].capture_gain / 100.0
		       : (double) nodes[i].volume,
		       nodes[i].plugged ? "yes" : "no",
		       nodes[i].left_right_swapped ? "yes" : "no",
		       (long) nodes[i].plugged_time.tv_sec,
		       nodes[i].active_hotword_model,
		       nodes[i].type,
		       nodes[i].active ? '*' : ' ',
		       nodes[i].name);
}

static void print_device_lists(struct cras_client *client)
{
	struct cras_iodev_info devs[MAX_IODEVS];
	struct cras_ionode_info nodes[MAX_IONODES];
	size_t num_devs, num_nodes;
	int rc;

	num_devs = MAX_IODEVS;
	num_nodes = MAX_IONODES;
	rc = cras_client_get_output_devices(client, devs, nodes, &num_devs,
					    &num_nodes);
	if (rc < 0)
		return;
	printf("Output Devices:\n");
	print_dev_info(devs, num_devs);
	printf("Output Nodes:\n");
	print_node_info(nodes, num_nodes, 0);

	num_devs = MAX_IODEVS;
	num_nodes = MAX_IONODES;
	rc = cras_client_get_input_devices(client, devs, nodes, &num_devs,
					   &num_nodes);
	printf("Input Devices:\n");
	print_dev_info(devs, num_devs);
	printf("Input Nodes:\n");
	print_node_info(nodes, num_nodes, 1);
}

static void print_attached_client_list(struct cras_client *client)
{
	struct cras_attached_client_info clients[MAX_ATTACHED_CLIENTS];
	size_t i;
	int num_clients;

	num_clients = cras_client_get_attached_clients(client,
						       clients,
						       MAX_ATTACHED_CLIENTS);
	if (num_clients < 0)
		return;
	num_clients = MIN(num_clients, MAX_ATTACHED_CLIENTS);
	printf("Attached clients:\n");
	printf("\tID\tpid\tuid\n");
	for (i = 0; i < num_clients; i++)
		printf("\t%u\t%d\t%d\n",
		       clients[i].id,
		       clients[i].pid,
		       clients[i].gid);
}

static void print_active_stream_info(struct cras_client *client)
{
	struct timespec ts;
	unsigned num_streams;

	num_streams = cras_client_get_num_active_streams(client, &ts);
	printf("Num active streams: %u\n", num_streams);
	printf("Last audio active time: %llu, %llu\n",
	       (long long)ts.tv_sec, (long long)ts.tv_nsec);
}

static void print_system_volumes(struct cras_client *client)
{
	printf("System Volume (0-100): %zu %s\n"
	       "Capture Gain (%.2f - %.2f): %.2fdB %s\n",
	       cras_client_get_system_volume(client),
	       cras_client_get_system_muted(client) ? "(Muted)" : "",
	       cras_client_get_system_min_capture_gain(client) / 100.0,
	       cras_client_get_system_max_capture_gain(client) / 100.0,
	       cras_client_get_system_capture_gain(client) / 100.0,
	       cras_client_get_system_capture_muted(client) ? "(Muted)" : "");
}

static void print_user_muted(struct cras_client *client)
{
	printf("User muted: %s\n",
	       cras_client_get_user_muted(client) ? "Muted" : "Not muted");
}

static void show_alog_tag(const struct audio_thread_event_log *log,
			  unsigned int tag_idx)
{
	unsigned int tag = (log->log[tag_idx].tag_sec >> 24) & 0xff;
	unsigned int sec = log->log[tag_idx].tag_sec & 0x00ffffff;
	unsigned int nsec = log->log[tag_idx].nsec;
	unsigned int data1 = log->log[tag_idx].data1;
	unsigned int data2 = log->log[tag_idx].data2;
	unsigned int data3 = log->log[tag_idx].data3;

	/* Skip unused log entries. */
	if (log->log[tag_idx].tag_sec == 0 && log->log[tag_idx].nsec == 0)
		return;

	printf("%10u.%09u  ", sec, nsec);

	switch (tag) {
	case AUDIO_THREAD_WAKE:
		printf("%-30s num_fds:%d\n", "WAKE", (int)data1);
		break;
	case AUDIO_THREAD_SLEEP:
		printf("%-30s sleep:%09d.%09d longest_wake:%09d\n",
		       "SLEEP", (int)data1, (int)data2, (int)data3);
		break;
	case AUDIO_THREAD_READ_AUDIO:
		printf("%-30s dev:%u hw_level:%u read:%u\n",
		       "READ_AUDIO", data1, data2, data3);
		break;
	case AUDIO_THREAD_READ_AUDIO_TSTAMP:
		printf("%-30s dev:%u tstamp:%09d.%09d\n",
		       "READ_AUDIO_TSTAMP", data1, (int)data2, (int)data3);
		break;
	case AUDIO_THREAD_READ_AUDIO_DONE:
		printf("%-30s read_remainder:%u\n", "READ_AUDIO_DONE", data1);
		break;
	case AUDIO_THREAD_READ_OVERRUN:
		printf("%-30s dev:%u stream:%x num_overruns:%u\n",
		       "READ_AUDIO_OVERRUN", data1, data2, data3);
		break;
	case AUDIO_THREAD_FILL_AUDIO:
		printf("%-30s dev:%u hw_level:%u\n",
		       "FILL_AUDIO", data1, data2);
		break;
	case AUDIO_THREAD_FILL_AUDIO_TSTAMP:
		printf("%-30s dev:%u tstamp:%09d.%09d\n",
		       "FILL_AUDIO_TSTAMP", data1, (int)data2, (int)data3);
		break;
	case AUDIO_THREAD_FILL_AUDIO_DONE:
		printf("%-30s hw_level:%u total_written:%u min_cb_level:%u\n",
		       "FILL_AUDIO_DONE", data1, data2, data3);
		break;
	case AUDIO_THREAD_WRITE_STREAMS_WAIT:
		printf("%-30s stream:%x\n", "WRITE_STREAMS_WAIT", data1);
		break;
	case AUDIO_THREAD_WRITE_STREAMS_WAIT_TO:
		printf("%-30s\n", "WRITE_STREAMS_WAIT_TO");
		break;
	case AUDIO_THREAD_WRITE_STREAMS_MIX:
		printf("%-30s write_limit:%u max_offset:%u\n",
		       "WRITE_STREAMS_MIX", data1, data2);
		break;
	case AUDIO_THREAD_WRITE_STREAMS_MIXED:
		printf("%-30s write_limit:%u\n", "WRITE_STREAMS_MIXED", data1);
		break;
	case AUDIO_THREAD_WRITE_STREAMS_STREAM:
		printf("%-30s id:%x shm_frames:%u cb_pending:%u\n",
		       "WRITE_STREAMS_STREAM", data1, data2, data3);
		break;
	case AUDIO_THREAD_FETCH_STREAM:
		printf("%-30s id:%x cbth:%u delay:%u\n",
		       "WRITE_STREAMS_FETCH_STREAM", data1, data2, data3);
		break;
	case AUDIO_THREAD_STREAM_ADDED:
		printf("%-30s id:%x dev:%u\n",
		       "STREAM_ADDED", data1, data2);
		break;
	case AUDIO_THREAD_STREAM_REMOVED:
		printf("%-30s id:%x\n", "STREAM_REMOVED", data1);
		break;
	case AUDIO_THREAD_A2DP_ENCODE:
		printf("%-30s proc:%d queued:%u readable:%u\n",
		       "A2DP_ENCODE", data1, data2, data3);
		break;
	case AUDIO_THREAD_A2DP_WRITE:
		printf("%-30s written:%d queued:%u\n",
		       "A2DP_WRITE", data1, data2);
		break;
	case AUDIO_THREAD_DEV_STREAM_MIX:
		printf("%-30s written:%u read:%u\n",
		       "DEV_STREAM_MIX", data1, data2);
		break;
	case AUDIO_THREAD_CAPTURE_POST:
		printf("%-30s stream:%x thresh:%u rd_buf:%u\n",
		       "CAPTURE_POST", data1, data2, data3);
		break;
	case AUDIO_THREAD_CAPTURE_WRITE:
		printf("%-30s stream:%x write:%u shm_fr:%u\n",
		       "CAPTURE_WRITE", data1, data2, data3);
		break;
	case AUDIO_THREAD_CONV_COPY:
		printf("%-30s wr_buf:%u shm_writable:%u offset:%u\n",
		       "CONV_COPY", data1, data2, data3);
		break;
	case AUDIO_THREAD_STREAM_SLEEP_TIME:
		printf("%-30s id:%x wake:%09u.%09d\n",
		       "STREAM_SLEEP_TIME", data1, (int)data2, (int)data3);
		break;
	case AUDIO_THREAD_STREAM_SLEEP_ADJUST:
		printf("%-30s id:%x from:%09u.%09d\n",
		       "STREAM_SLEEP_ADJUST", data1, data2, data3);
		break;
	case AUDIO_THREAD_STREAM_SKIP_CB:
		printf("%-30s id:%x write_offset_0:%u write_offset_1:%u\n",
		       "STREAM_SKIP_CB", data1, data2, data3);
		break;
	case AUDIO_THREAD_DEV_SLEEP_TIME:
		printf("%-30s dev:%u wake:%09u.%09d\n",
		       "DEV_SLEEP_TIME", data1, data2, data3);
		break;
	case AUDIO_THREAD_SET_DEV_WAKE:
		printf("%-30s dev:%u hw_level:%u sleep:%u\n",
		       "SET_DEV_WAKE", data1, data2, data3);
		break;
	case AUDIO_THREAD_DEV_ADDED:
		printf("%-30s dev:%u\n", "DEV_ADDED", data1);
		break;
	case AUDIO_THREAD_DEV_REMOVED:
		printf("%-30s dev:%u\n", "DEV_REMOVED", data1);
		break;
	case AUDIO_THREAD_IODEV_CB:
		printf("%-30s is_write:%u\n", "IODEV_CB", data1);
		break;
	case AUDIO_THREAD_PB_MSG:
		printf("%-30s msg_id:%u\n", "PB_MSG", data1);
		break;
	case AUDIO_THREAD_ODEV_NO_STREAMS:
		printf("%-30s dev:%u\n",
		       "ODEV_NO_STREAMS", data1);
		break;
	case AUDIO_THREAD_ODEV_LEAVE_NO_STREAMS:
		printf("%-30s dev:%u\n",
		       "ODEV_LEAVE_NO_STREAMS", data1);
		break;
	case AUDIO_THREAD_ODEV_START:
		printf("%-30s dev:%u min_cb_level:%u\n",
		       "ODEV_START", data1, data2);
		break;
	case AUDIO_THREAD_FILL_ODEV_ZEROS:
		printf("%-30s dev:%u write:%u\n",
		       "FILL_ODEV_ZEROS", data1, data2);
		break;
	case AUDIO_THREAD_ODEV_DEFAULT_NO_STREAMS:
		printf("%-30s dev:%u hw_level:%u target:%u\n",
		       "DEFAULT_NO_STREAMS", data1, data2, data3);
		break;
	case AUDIO_THREAD_SEVERE_UNDERRUN:
		printf("%-30s dev:%u\n", "SEVERE_UNDERRUN", data1);
		break;
	default:
		printf("%-30s tag:%u\n","UNKNOWN", tag);
		break;
	}
}

static void audio_debug_info(struct cras_client *client)
{
	const struct audio_debug_info *info;
	int i, j;

	info = cras_client_get_audio_debug_info(client);
	if (!info)
		return;

	printf("Audio Debug Stats:\n");
	printf("-------------devices------------\n");
	if (info->num_devs > MAX_DEBUG_DEVS)
		return;

	for (i = 0; i < info->num_devs; i++) {
		printf("%s dev: %s\n",
		       (info->devs[i].direction == CRAS_STREAM_INPUT)
				? "Input" : "Output",
		       info->devs[i].dev_name);
		printf("buffer_size: %u\n"
		       "min_buffer_level: %u\n"
		       "min_cb_level: %u\n"
		       "max_cb_level: %u\n"
		       "frame_rate: %u\n"
		       "num_channels: %u\n"
		       "est_rate_ratio: %lf\n"
		       "num_underruns: %u\n"
		       "num_severe_underruns: %u\n",
		       (unsigned int)info->devs[i].buffer_size,
		       (unsigned int)info->devs[i].min_buffer_level,
		       (unsigned int)info->devs[i].min_cb_level,
		       (unsigned int)info->devs[i].max_cb_level,
		       (unsigned int)info->devs[i].frame_rate,
		       (unsigned int)info->devs[i].num_channels,
		       info->devs[i].est_rate_ratio,
		       (unsigned int)info->devs[i].num_underruns,
		       (unsigned int)info->devs[i].num_severe_underruns);
		printf("\n");
	}

	printf("-------------stream_dump------------\n");
	if (info->num_streams > MAX_DEBUG_STREAMS)
		return;

	for (i = 0; i < info->num_streams; i++) {
		int channel;
		printf("stream: %llx dev: %u\n",
		       (unsigned long long)info->streams[i].stream_id,
		       (unsigned int)info->streams[i].dev_idx);
		printf("direction: %s\n",
		       (info->streams[i].direction == CRAS_STREAM_INPUT)
				? "Input" : "Output");
		printf("stream_type: %s\n",
		       cras_stream_type_str(info->streams[i].stream_type));
		printf("buffer_frames: %u\n"
		       "cb_threshold: %u\n"
		       "frame_rate: %u\n"
		       "num_channels: %u\n"
		       "longest_fetch_sec: %u.%09u\n"
		       "num_overruns: %u\n",
		       (unsigned int)info->streams[i].buffer_frames,
		       (unsigned int)info->streams[i].cb_threshold,
		       (unsigned int)info->streams[i].frame_rate,
		       (unsigned int)info->streams[i].num_channels,
		       (unsigned int)info->streams[i].longest_fetch_sec,
		       (unsigned int)info->streams[i].longest_fetch_nsec,
		       (unsigned int)info->streams[i].num_overruns);
		printf("channel map:");
		for (channel = 0; channel < CRAS_CH_MAX; channel++)
			printf("%d ", info->streams[i].channel_layout[channel]);
		printf("\n\n");
	}

	printf("Audio Thread Event Log:\n");

	j = info->log.write_pos;
	i = 0;
	printf("start at %d\n", j);
	for (; i < info->log.len; i++) {
		show_alog_tag(&info->log, j);
		j++;
		j %= info->log.len;
	}

	/* Signal main thread we are done after the last chunk. */
	pthread_mutex_lock(&done_mutex);
	pthread_cond_signal(&done_cond);
	pthread_mutex_unlock(&done_mutex);
}

static int start_stream(struct cras_client *client,
			cras_stream_id_t *stream_id,
			struct cras_stream_params *params,
			float stream_volume)
{
	int rc;

	if (pin_device_id)
		rc = cras_client_add_pinned_stream(client, pin_device_id,
						   stream_id, params);
	else
		rc = cras_client_add_stream(client, stream_id, params);
	if (rc < 0) {
		fprintf(stderr, "adding a stream %d\n", rc);
		return rc;
	}
	return cras_client_set_stream_volume(client, *stream_id, stream_volume);
}

static int parse_channel_layout(char *channel_layout_str,
				int8_t channel_layout[CRAS_CH_MAX])
{
	int i = 0;
	char *chp;

	chp = strtok(channel_layout_str, ",");
	while (chp && i < CRAS_CH_MAX) {
		channel_layout[i++] = atoi(chp);
		chp = strtok(NULL, ",");
	}

	return 0;
}

static int run_file_io_stream(struct cras_client *client,
			      int fd,
			      enum CRAS_STREAM_DIRECTION direction,
			      size_t block_size,
			      enum CRAS_STREAM_TYPE stream_type,
			      size_t rate,
			      size_t num_channels,
			      uint32_t flags,
			      int is_loopback)
{
	int rc, tty;
	struct cras_stream_params *params;
	cras_unified_cb_t aud_cb;
	cras_stream_id_t stream_id = 0;
	int stream_playing = 0;
	int *pfd = malloc(sizeof(*pfd));
	*pfd = fd;
	fd_set poll_set;
	struct timespec sleep_ts;
	float volume_scaler = 1.0;
	size_t sys_volume = 100;
	long cap_gain = 0;
	int mute = 0;
	int8_t layout[CRAS_CH_MAX];

	/* Set the sleep interval between latency/RMS prints. */
	sleep_ts.tv_sec = 1;
	sleep_ts.tv_nsec = 0;

	/* Open the pipe file descriptor. */
	rc = pipe(pipefd);
	if (rc == -1) {
		perror("failed to open pipe");
		return -errno;
	}

	/* Reset the total RMS value. */
	total_rms_sqr_sum = 0;
	total_rms_size = 0;

	if (direction == CRAS_STREAM_INPUT) {
		if (flags == HOTWORD_STREAM)
			aud_cb = got_hotword;
		else
			aud_cb = got_samples;
	} else {
		aud_cb = put_samples;
	}

	if (fd == 0) {
		if (direction != CRAS_STREAM_OUTPUT)
			return -EINVAL;
		aud_cb = put_stdin_samples;
	}

	aud_format = cras_audio_format_create(SND_PCM_FORMAT_S16_LE, rate,
					      num_channels);
	if (aud_format == NULL)
		return -ENOMEM;

	if (channel_layout) {
		/* Set channel layout to format */
		parse_channel_layout(channel_layout, layout);
		cras_audio_format_set_channel_layout(aud_format, layout);
	}

	params = cras_client_unified_params_create(direction,
						   block_size,
						   stream_type,
						   flags,
						   pfd,
						   aud_cb,
						   stream_error,
						   aud_format);
	if (params == NULL)
		return -ENOMEM;

	cras_client_run_thread(client);
	if (is_loopback) {
		cras_client_connected_wait(client);
		pin_device_id = cras_client_get_first_dev_type_idx(client,
				CRAS_NODE_TYPE_POST_MIX_PRE_DSP,
				CRAS_STREAM_INPUT);
	}

	stream_playing =
		start_stream(client, &stream_id, params, volume_scaler) == 0;

	tty = open("/dev/tty", O_RDONLY);

	// There could be no terminal available when run in autotest.
	if (tty == -1)
		perror("warning: failed to open /dev/tty");

	while (keep_looping) {
		char input;
		int nread;

		FD_ZERO(&poll_set);
		if (tty >= 0)
			FD_SET(tty, &poll_set);
		FD_SET(pipefd[0], &poll_set);
		pselect(MAX(tty, pipefd[0]) + 1,
			&poll_set,
			NULL,
			NULL,
			show_latency || show_rms ? &sleep_ts : NULL,
			NULL);

		if (stream_playing && show_latency)
			print_last_latency();

		if (stream_playing && show_rms)
			print_last_rms();

		if (tty < 0 || !FD_ISSET(tty, &poll_set))
			continue;

		nread = read(tty, &input, 1);
		if (nread < 1) {
			fprintf(stderr, "Error reading stdin\n");
			return nread;
		}
		switch (input) {
		case 'p':
			pause_client = !pause_client;
			break;
		case 'i':
			pause_a_reply = 1;
			break;
		case 'q':
			terminate_stream_loop();
			break;
		case 's':
			if (stream_playing)
				break;

			/* If started by hand keep running after it finishes. */
			exit_after_done_playing = 0;

			stream_playing = start_stream(client,
						      &stream_id,
						      params,
						      volume_scaler) == 0;
			break;
		case 'r':
			if (!stream_playing)
				break;
			cras_client_rm_stream(client, stream_id);
			stream_playing = 0;
			break;
		case 'u':
			volume_scaler = MIN(volume_scaler + 0.1, 1.0);
			cras_client_set_stream_volume(client,
						      stream_id,
						      volume_scaler);
			break;
		case 'd':
			volume_scaler = MAX(volume_scaler - 0.1, 0.0);
			cras_client_set_stream_volume(client,
						      stream_id,
						      volume_scaler);
			break;
		case 'k':
			sys_volume = MIN(sys_volume + 1, 100);
			cras_client_set_system_volume(client, sys_volume);
			break;
		case 'j':
			sys_volume = sys_volume == 0 ? 0 : sys_volume - 1;
			cras_client_set_system_volume(client, sys_volume);
			break;
		case 'K':
			cap_gain = MIN(cap_gain + 100, 5000);
			cras_client_set_system_capture_gain(client, cap_gain);
			break;
		case 'J':
			cap_gain = cap_gain == -5000 ? -5000 : cap_gain - 100;
			cras_client_set_system_capture_gain(client, cap_gain);
			break;
		case 'm':
			mute = !mute;
			cras_client_set_system_mute(client, mute);
			break;
		case '@':
			print_device_lists(client);
			break;
		case '#':
			print_attached_client_list(client);
			break;
		case 'v':
			printf("Volume: %zu%s Min dB: %ld Max dB: %ld\n"
			       "Capture: %ld%s Min dB: %ld Max dB: %ld\n",
			       cras_client_get_system_volume(client),
			       cras_client_get_system_muted(client) ? "(Muted)"
								    : "",
			       cras_client_get_system_min_volume(client),
			       cras_client_get_system_max_volume(client),
			       cras_client_get_system_capture_gain(client),
			       cras_client_get_system_capture_muted(client) ?
						"(Muted)" : "",
			       cras_client_get_system_min_capture_gain(client),
			       cras_client_get_system_max_capture_gain(client));
			break;
		case '\'':
			play_short_sound_periods_left = play_short_sound_periods;
			break;
		case '\n':
			break;
		default:
			printf("Invalid key\n");
			break;
		}
	}

	if (show_total_rms)
		print_total_rms();

	cras_client_stop(client);

	cras_audio_format_destroy(aud_format);
	cras_client_stream_params_destroy(params);
	free(pfd);

	close(pipefd[0]);
	close(pipefd[1]);

	return 0;
}

static int run_capture(struct cras_client *client,
		       const char *file,
		       size_t block_size,
		       enum CRAS_STREAM_TYPE stream_type,
		       size_t rate,
		       size_t num_channels,
		       int is_loopback)
{
	int fd = open(file, O_CREAT | O_RDWR | O_TRUNC, 0666);
	if (fd == -1) {
		perror("failed to open file");
		return -errno;
	}

	run_file_io_stream(client, fd, CRAS_STREAM_INPUT, block_size,
			   stream_type, rate, num_channels, 0, is_loopback);

	close(fd);
	return 0;
}

static int run_playback(struct cras_client *client,
			const char *file,
			size_t block_size,
			enum CRAS_STREAM_TYPE stream_type,
			size_t rate,
			size_t num_channels)
{
	int fd;

	fd = open(file, O_RDONLY);
	if (fd == -1) {
		perror("failed to open file");
		return -errno;
	}

	run_file_io_stream(client, fd, CRAS_STREAM_OUTPUT, block_size,
			   stream_type, rate, num_channels, 0, 0);

	close(fd);
	return 0;
}

static int run_hotword(struct cras_client *client,
		       size_t block_size,
		       size_t rate)
{
	run_file_io_stream(client, -1, CRAS_STREAM_INPUT, block_size,
			   CRAS_STREAM_TYPE_DEFAULT, rate, 1, HOTWORD_STREAM,
			   0);
	return 0;
}
static void print_server_info(struct cras_client *client)
{
	cras_client_run_thread(client);
	cras_client_connected_wait(client); /* To synchronize data. */
	print_system_volumes(client);
	print_user_muted(client);
	print_device_lists(client);
	print_attached_client_list(client);
	print_active_stream_info(client);
}

static void print_audio_debug_info(struct cras_client *client)
{
	struct timespec wait_time;

	cras_client_run_thread(client);
	cras_client_connected_wait(client); /* To synchronize data. */
	cras_client_update_audio_debug_info(client, audio_debug_info);

	clock_gettime(CLOCK_REALTIME, &wait_time);
	wait_time.tv_sec += 2;

	pthread_mutex_lock(&done_mutex);
	pthread_cond_timedwait(&done_cond, &done_mutex, &wait_time);
	pthread_mutex_unlock(&done_mutex);
}

static void hotword_models_cb(struct cras_client *client,
			      const char *hotword_models)
{
	printf("Hotword models: %s\n", hotword_models);
}

static void print_hotword_models(struct cras_client *client,
				 cras_node_id_t id)
{
	struct timespec wait_time;

	cras_client_run_thread(client);
	cras_client_connected_wait(client);
	cras_client_get_hotword_models(client, id,
				       hotword_models_cb);

	clock_gettime(CLOCK_REALTIME, &wait_time);
	wait_time.tv_sec += 2;

	pthread_mutex_lock(&done_mutex);
	pthread_cond_timedwait(&done_cond, &done_mutex, &wait_time);
	pthread_mutex_unlock(&done_mutex);
}

static void check_output_plugged(struct cras_client *client, const char *name)
{
	cras_client_run_thread(client);
	cras_client_connected_wait(client); /* To synchronize data. */
	printf("%s\n",
	       cras_client_output_dev_plugged(client, name) ? "Yes" : "No");
}

/* Repeatedly mute and unmute the output until there is an error. */
static void mute_loop_test(struct cras_client *client, int auto_reconnect)
{
	int mute = 0;
	int rc;

	if (auto_reconnect)
		cras_client_run_thread(client);
	while(1) {
		rc = cras_client_set_user_mute(client, mute);
		printf("cras_client_set_user_mute(%d): %d\n", mute, rc);
		if (rc != 0 && !auto_reconnect)
			return;
		mute = !mute;
		sleep(2);
	}
}

static struct option long_options[] = {
	{"show_latency",	no_argument, &show_latency, 1},
	{"show_rms",            no_argument, &show_rms, 1},
	{"show_total_rms",      no_argument, &show_total_rms, 1},
	{"select_input",        required_argument,      0, 'a'},
	{"block_size",		required_argument,	0, 'b'},
	{"capture_file",	required_argument,	0, 'c'},
	{"duration_seconds",	required_argument,	0, 'd'},
	{"dump_dsp",            no_argument,            0, 'f'},
	{"capture_gain",        required_argument,      0, 'g'},
	{"help",                no_argument,            0, 'h'},
	{"dump_server_info",    no_argument,            0, 'i'},
	{"check_output_plugged",required_argument,      0, 'j'},
	{"add_active_input",	required_argument,	0, 'k'},
	{"add_active_output",	required_argument,	0, 't'},
	{"loopback_file",	required_argument,	0, 'l'},
	{"dump_audio_thread",   no_argument,            0, 'm'},
	{"num_channels",        required_argument,      0, 'n'},
	{"channel_layout",      required_argument,      0, 'o'},
	{"playback_file",	required_argument,	0, 'p'},
	{"user_mute",           required_argument,      0, 'q'},
	{"rate",		required_argument,	0, 'r'},
	{"reload_dsp",          no_argument,            0, 's'},
	{"mute",                required_argument,      0, 'u'},
	{"volume",              required_argument,      0, 'v'},
	{"set_node_volume",	required_argument,      0, 'w'},
	{"plug",                required_argument,      0, 'x'},
	{"select_output",       required_argument,      0, 'y'},
	{"playback_delay_us",   required_argument,      0, 'z'},
	{"capture_mute",        required_argument,      0, '0'},
	{"rm_active_input",	required_argument,	0, '1'},
	{"rm_active_output",	required_argument,	0, '2'},
	{"swap_left_right",     required_argument,      0, '3'},
	{"version",             no_argument,            0, '4'},
	{"add_test_dev",        required_argument,      0, '5'},
	{"test_hotword_file",   required_argument,      0, '6'},
	{"listen_for_hotword",  no_argument,            0, '7'},
	{"pin_device",		required_argument,	0, '8'},
	{"suspend",		required_argument,	0, '9'},
	{"set_node_gain",	required_argument,	0, ':'},
	{"play_short_sound",	required_argument,	0, '!'},
	{"config_global_remix", required_argument,	0, ';'},
	{"set_hotword_model",	required_argument,	0, '<'},
	{"get_hotword_models",	required_argument,	0, '>'},
	{"syslog_mask",		required_argument,	0, 'L'},
	{"mute_loop_test",	required_argument,	0, 'M'},
	{"stream_type",		required_argument,	0, 'T'},
	{0, 0, 0, 0}
};

static void show_usage()
{
	printf("--add_active_input <N>:<M> - Add the ionode with the given id"
	       "to active input device list\n");
	printf("--add_active_output <N>:<M> - Add the ionode with the given id"
	       "to active output device list\n");
	printf("--add_test_dev <type> - add a test iodev.\n");
	printf("--block_size <N> - The number for frames per callback(dictates latency).\n");
	printf("--capture_file <name> - Name of file to record to.\n");
	printf("--capture_gain <dB> - Set system caputre gain in dB*100 (100 = 1dB).\n");
	printf("--capture_mute <0|1> - Set capture mute state.\n");
	printf("--channel_layout <layout_str> - Set multiple channel layout.\n");
	printf("--check_output_plugged <output name> - Check if the output is plugged in\n");
	printf("--dump_audio_thread - Dumps audio thread info.\n");
	printf("--dump_dsp - Print status of dsp to syslog.\n");
	printf("--dump_server_info - Print status of the server.\n");
	printf("--duration_seconds <N> - Seconds to record or playback.\n");
	printf("--get_hotword_models <N>:<M> - Get the supported hotword models of node\n");
	printf("--help - Print this message.\n");
	printf("--listen_for_hotword - Listen for a hotword if supported\n");
	printf("--loopback_file <name> - Name of file to record loopback to.\n");
	printf("--mute <0|1> - Set system mute state.\n");
	printf("--mute_loop_test <0|1> - Continuously loop mute/umute. Argument: 0 - stop on error.\n"
	       "                         1 - automatically reconnect to CRAS.\n");
	printf("--num_channels <N> - Two for stereo.\n");
	printf("--pin_device <N> - Playback/Capture only on the given device."
	       "\n");
	printf("--playback_file <name> - Name of file to play, "
	       "\"-\" to playback raw audio from stdin.\n");
	printf("--play_short_sound <N> - Plays the content in the file for N periods when ' is pressed.\n");
	printf("--plug <N>:<M>:<0|1> - Set the plug state (0 or 1) for the"
	       " ionode with the given index M on the device with index N\n");
	printf("--rate <N> - Specifies the sample rate in Hz.\n");
	printf("--reload_dsp - Reload dsp configuration from the ini file\n");
	printf("--rm_active_input <N>:<M> - Removes the ionode with the given"
	       "id from active input device list\n");
	printf("--rm_active_output <N>:<M> - Removes the ionode with the given"
	       "id from active output device list\n");
	printf("--select_input <N>:<M> - Select the ionode with the given id as preferred input\n");
	printf("--select_output <N>:<M> - Select the ionode with the given id as preferred output\n");
	printf("--set_hotword_model <N>:<M>:<model> - Set the model to node\n");
	printf("--playback_delay_us <N> - Set the time in us to delay a reply for playback when i is pressed\n");
	printf("--set_node_volume <N>:<M>:<0-100> - Set the volume of the ionode with the given id\n");
	printf("--show_latency - Display latency while playing or recording.\n");
	printf("--show_rms - Display RMS value of loopback stream.\n");
	printf("--show_total_rms - Display total RMS value of loopback stream at the end.\n");
	printf("--suspend <0|1> - Set audio suspend state.\n");
	printf("--swap_left_right <N>:<M>:<0|1> - Swap or unswap (1 or 0) the"
	       " left and right channel for the ionode with the given index M"
	       " on the device with index N\n");
	printf("--stream_type <N> - Specify the type of the stream.\n");
	printf("--syslog_mask <n> - Set the syslog mask to the given log level.\n");
	printf("--test_hotword_file <N>:<filename> - Use filename as a hotword buffer for device N\n");
	printf("--user_mute <0|1> - Set user mute state.\n");
	printf("--version - Print the git commit ID that was used to build the client.\n");
	printf("--volume <0-100> - Set system output volume.\n");
}

int main(int argc, char **argv)
{
	struct cras_client *client;
	int c, option_index;
	size_t block_size = NOT_ASSIGNED;
	size_t rate = 48000;
	size_t num_channels = 2;
	float duration_seconds = 0;
	const char *capture_file = NULL;
	const char *playback_file = NULL;
	const char *loopback_file = NULL;
	enum CRAS_STREAM_TYPE stream_type = CRAS_STREAM_TYPE_DEFAULT;
	int rc = 0;

	option_index = 0;
	openlog("cras_test_client", LOG_PERROR, LOG_USER);
	setlogmask(LOG_UPTO(LOG_INFO));

	rc = cras_client_create(&client);
	if (rc < 0) {
		fprintf(stderr, "Couldn't create client.\n");
		return rc;
	}

	rc = cras_client_connect_timeout(client, 1000);
	if (rc) {
		fprintf(stderr, "Couldn't connect to server.\n");
		goto destroy_exit;
	}

	while (1) {
		c = getopt_long(argc, argv, "o:s:",
				long_options, &option_index);
		if (c == -1)
			break;
		switch (c) {
		case 'c':
			capture_file = optarg;
			break;
		case 'p':
			playback_file = optarg;
			break;
		case 'l':
			loopback_file = optarg;
			break;
		case 'b':
			block_size = atoi(optarg);
			break;
		case 'r':
			rate = atoi(optarg);
			break;
		case 'n':
			num_channels = atoi(optarg);
			break;
		case 'd':
			duration_seconds = atof(optarg);
			break;
		case 'u': {
			int mute = atoi(optarg);
			rc = cras_client_set_system_mute(client, mute);
			if (rc < 0) {
				fprintf(stderr, "problem setting mute\n");
				goto destroy_exit;
			}
			break;
		}
		case 'q': {
			int mute = atoi(optarg);
			rc = cras_client_set_user_mute(client, mute);
			if (rc < 0) {
				fprintf(stderr, "problem setting mute\n");
				goto destroy_exit;
			}
			break;
		}
		case 'v': {
			int volume = atoi(optarg);
			volume = MIN(100, MAX(0, volume));
			rc = cras_client_set_system_volume(client, volume);
			if (rc < 0) {
				fprintf(stderr, "problem setting volume\n");
				goto destroy_exit;
			}
			break;
		}
		case 'g': {
			long gain = atol(optarg);
			rc = cras_client_set_system_capture_gain(client, gain);
			if (rc < 0) {
				fprintf(stderr, "problem setting capture\n");
				goto destroy_exit;
			}
			break;
		}
		case 'j':
			check_output_plugged(client, optarg);
			break;
		case 's':
			cras_client_reload_dsp(client);
			break;
		case 'f':
			cras_client_dump_dsp_info(client);
			break;
		case 'i':
			print_server_info(client);
			break;
		case 'h':
			show_usage();
			break;
		case 'x': {
			int dev_index = atoi(strtok(optarg, ":"));
			int node_index = atoi(strtok(NULL, ":"));
			int value = atoi(strtok(NULL, ":")) ;
			cras_node_id_t id = cras_make_node_id(dev_index,
							      node_index);
			enum ionode_attr attr = IONODE_ATTR_PLUGGED;
			cras_client_set_node_attr(client, id, attr, value);
			break;
		}
		case 'y':
		case 'a': {
			int dev_index = atoi(strtok(optarg, ":"));
			int node_index = atoi(strtok(NULL, ":"));
			cras_node_id_t id = cras_make_node_id(dev_index,
							      node_index);

			enum CRAS_STREAM_DIRECTION direction = (c == 'y') ?
				CRAS_STREAM_OUTPUT : CRAS_STREAM_INPUT;
			cras_client_select_node(client, direction, id);
			break;
		}
		case 'z':
			pause_in_playback_reply = atoi(optarg);
			break;
		case 'k':
		case 't':
		case '1':
		case '2':{
			int dev_index = atoi(strtok(optarg, ":"));
			int node_index = atoi(strtok(NULL, ":"));
			enum CRAS_STREAM_DIRECTION dir;
			cras_node_id_t id = cras_make_node_id(dev_index,
							      node_index);

			if (c == 't' || c == '2')
				dir = CRAS_STREAM_OUTPUT;
			else
				dir = CRAS_STREAM_INPUT;

			if (c == 'k' || c == 't')
				cras_client_add_active_node(client, dir, id);
			else
				cras_client_rm_active_node(client, dir, id);
			break;
		}
		case ':':
		case 'w': {
			const char *s;
			int dev_index;
			int node_index;
			int value;

			s = strtok(optarg, ":");
			if (!s) {
				show_usage();
				return -EINVAL;
			}
			dev_index = atoi(s);

			s = strtok(NULL, ":");
			if (!s) {
				show_usage();
				return -EINVAL;
			}
			node_index = atoi(s);

			s = strtok(NULL, ":");
			if (!s) {
				show_usage();
				return -EINVAL;
			}
			value = atoi(s) ;

			cras_node_id_t id = cras_make_node_id(dev_index,
							      node_index);

			if (c == 'w')
				cras_client_set_node_volume(client, id, value);
			else
				cras_client_set_node_capture_gain(
						client, id, value);
			break;
		}
		case '0': {
			int mute = atoi(optarg);
			rc = cras_client_set_system_capture_mute(client, mute);
			if (rc < 0) {
				fprintf(stderr, "problem setting mute\n");
				goto destroy_exit;
			}
			break;
		}
		case 'm':
			print_audio_debug_info(client);
			break;
		case 'o':
			channel_layout = optarg;
			break;
		case '3': {
			int dev_index = atoi(strtok(optarg, ":"));
			int node_index = atoi(strtok(NULL, ":"));
			int value = atoi(strtok(NULL, ":")) ;
			cras_node_id_t id = cras_make_node_id(dev_index,
							      node_index);
			cras_client_swap_node_left_right(client, id, value);
			break;
		}
		case '4':
			printf("%s\n", VCSID);
			break;
		case '5': {
			cras_client_add_test_iodev(client, atoi(optarg));
			break;
		}
		case '6': {
			int dev_index = atoi(strtok(optarg, ":"));
			const char *file_name = strtok(NULL, ":");
			cras_client_test_iodev_command(client, dev_index,
					TEST_IODEV_CMD_HOTWORD_TRIGGER,
					strlen(file_name) + 1,
					(uint8_t *)file_name);
			break;
		}
		case '7': {
			run_hotword(client, 4096, 16000);
			break;
		}
		case '8':
			pin_device_id = atoi(optarg);
			break;
		case '9': {
			int suspend = atoi(optarg);
			cras_client_set_suspend(client, suspend);
			break;
		}
		case '!': {
			play_short_sound = 1;
			play_short_sound_periods = atoi(optarg);
			break;
		}
		case ';': {
			char *s;
			int nch;
			int size = 0;
			float *coeff;

			s = strtok(optarg, ":");
			nch = atoi(s);
			coeff = (float *)calloc(nch * nch,
						sizeof(*coeff));
			for (size = 0; size < nch * nch; size++) {
				s = strtok(NULL, ",");
				if (NULL == s)
					break;
				coeff[size] = atof(s);
			}
			cras_client_config_global_remix(client, nch, coeff);
			free(coeff);
			break;
		}
		case '<':
		case '>': {
			char *s;
			int dev_index;
			int node_index;

			s = strtok(optarg, ":");
			if (!s) {
				show_usage();
				return -EINVAL;
			}
			dev_index = atoi(s);

			s = strtok(NULL, ":");
			if (!s) {
				show_usage();
				return -EINVAL;
			}
			node_index = atoi(s);

			s = strtok(NULL, ":");
			if (!s && c == ';') {
				show_usage();
				return -EINVAL;
			}

			cras_node_id_t id = cras_make_node_id(dev_index,
							      node_index);
			if (c == '<')
				cras_client_set_hotword_model(client, id, s);
			else
				print_hotword_models(client, id);
			break;
		}
		case 'L': {
			int log_level = atoi(optarg);

			setlogmask(LOG_UPTO(log_level));
			break;
		}
		case 'M':
			mute_loop_test(client, atoi(optarg));
			break;
		case 'T':
			stream_type = atoi(optarg);
			break;
		default:
			break;
		}
	}

	duration_frames = duration_seconds * rate;
	if (block_size == NOT_ASSIGNED)
		block_size = get_block_size(PLAYBACK_BUFFERED_TIME_IN_US, rate);

	if (capture_file != NULL) {
		if (strcmp(capture_file, "-") == 0)
			rc = run_file_io_stream(client, 1, CRAS_STREAM_INPUT,
					block_size, stream_type, rate,
					num_channels, 0, 0);
		else
			rc = run_capture(client, capture_file, block_size,
					 stream_type, rate, num_channels, 0);
	} else if (playback_file != NULL) {
		if (strcmp(playback_file, "-") == 0)
			rc = run_file_io_stream(client, 0, CRAS_STREAM_OUTPUT,
					block_size, stream_type, rate,
					num_channels, 0, 0);
		else
			rc = run_playback(client, playback_file, block_size,
					  stream_type, rate, num_channels);
	} else if (loopback_file != NULL) {
		rc = run_capture(client, loopback_file, block_size,
				 stream_type, rate, num_channels, 1);
	}

destroy_exit:
	cras_client_destroy(client);
	return rc;
}