| /* 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 <inttypes.h> |
| #include <limits.h> |
| #include <math.h> |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <sys/mman.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; |
| |
| static int effect_aec = 0; |
| static int effect_ns = 0; |
| static int effect_agc = 0; |
| static int effect_vad = 0; |
| static char *aecdump_file = NULL; |
| static char time_str[128]; |
| |
| /* Sleep interval between cras_client_read_atlog calls. */ |
| static const struct timespec follow_atlog_sleep_ts = { |
| 0, 50 * 1000 * 1000 /* 50 ms. */ |
| }; |
| |
| /* 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; |
| struct { |
| char *name; |
| snd_pcm_format_t format; |
| } supported_formats[] = { |
| { "S16_LE", SND_PCM_FORMAT_S16_LE }, |
| { "S24_LE", SND_PCM_FORMAT_S24_LE }, |
| { "S32_LE", SND_PCM_FORMAT_S32_LE }, |
| { NULL, 0 }, |
| }; |
| |
| 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; |
| } |
| } |
| |
| static void fill_time_offset(time_t *sec_offset, int32_t *nsec_offset) |
| { |
| struct timespec mono_time, real_time; |
| |
| clock_gettime(CLOCK_MONOTONIC_RAW, &mono_time); |
| clock_gettime(CLOCK_REALTIME, &real_time); |
| *sec_offset = real_time.tv_sec - mono_time.tv_sec; |
| *nsec_offset = real_time.tv_nsec - mono_time.tv_nsec; |
| } |
| |
| /* 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; |
| } |
| |
| /* Parses a string with format <N>:<M> into a node id*/ |
| static int parse_node_id(char *input, cras_node_id_t *id_out) |
| { |
| const char *s; |
| char *endptr; |
| int dev_index; |
| int node_index; |
| |
| if (!id_out) |
| return -EINVAL; |
| |
| s = strtok(input, ":"); |
| if (!s) |
| return -EINVAL; |
| dev_index = strtol(s, &endptr, 10); |
| if (*endptr) |
| return -EINVAL; |
| |
| s = strtok(NULL, ":"); |
| if (!s) |
| return -EINVAL; |
| node_index = strtol(s, &endptr, 10); |
| if (*endptr) |
| return -EINVAL; |
| |
| *id_out = cras_make_node_id(dev_index, node_index); |
| return 0; |
| } |
| |
| /* Parses a string with format <N>:<M>:<0-100> into a node id and a value */ |
| static int parse_node_id_with_value(char *input, cras_node_id_t *id_out, |
| int *value_out) |
| { |
| const char *s; |
| char *endptr; |
| int dev_index; |
| int node_index; |
| long int value; |
| |
| if (!id_out || !value_out) |
| return -EINVAL; |
| |
| s = strtok(input, ":"); |
| if (!s) |
| return -EINVAL; |
| dev_index = strtol(s, &endptr, 10); |
| if (*endptr) |
| return -EINVAL; |
| |
| s = strtok(NULL, ":"); |
| if (!s) |
| return -EINVAL; |
| node_index = strtol(s, &endptr, 10); |
| if (*endptr) |
| return -EINVAL; |
| |
| s = strtok(NULL, ":"); |
| if (!s) |
| return -EINVAL; |
| value = strtol(s, &endptr, 10); |
| if (*endptr) |
| return -EINVAL; |
| if (value > INT_MAX || value < INT_MIN) |
| return -EOVERFLOW; |
| |
| *id_out = cras_make_node_id(dev_index, node_index); |
| *value_out = value; |
| 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 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, (size_t)frames * (size_t)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\tMaxCha\tName\n"); |
| for (i = 0; i < num_devs; i++) |
| printf("\t%u\t%u\t%s\n", devs[i].idx, |
| devs[i].max_supported_channels, devs[i].name); |
| } |
| |
| static void print_node_info(struct cras_client *client, |
| const struct cras_ionode_info *nodes, int num_nodes, |
| int is_input) |
| { |
| unsigned i; |
| |
| printf("\tStable Id\t ID\t%4s UI Plugged\tL/R swapped\t " |
| "Time Hotword\tType\t\tMaxCha Name\n", |
| is_input ? "Gain" : " Vol"); |
| for (i = 0; i < num_nodes; i++) { |
| char max_channels_str[7]; |
| if (is_input) { |
| // Print "X" as don't-care for input nodes because |
| // cras_client_get_max_supported_channels() is only valid for outputs. |
| strcpy(max_channels_str, " X"); |
| } else { |
| uint32_t max_channels; |
| int rc = cras_client_get_max_supported_channels( |
| client, |
| cras_make_node_id(nodes[i].iodev_idx, |
| nodes[i].ionode_idx), |
| &max_channels); |
| if (rc) |
| max_channels = 0; |
| sprintf(max_channels_str, "%6u", max_channels); |
| } |
| printf("\t(%08x)\t%u:%u\t%5g %f %7s\t%14s\t%10ld %-7s\t%-16s%-6s%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].ui_gain_scaler, 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, |
| max_channels_str, 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(client, 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(client, 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 Muted : %s\n", |
| cras_client_get_system_volume(client), |
| cras_client_get_system_muted(client) ? "(Muted)" : "", |
| cras_client_get_system_capture_muted(client) ? "Muted" : |
| "Not muted"); |
| } |
| |
| static void print_user_muted(struct cras_client *client) |
| { |
| printf("User muted: %s\n", |
| cras_client_get_user_muted(client) ? "Muted" : "Not muted"); |
| } |
| |
| /* |
| * Convert time value from one clock to the other using given offset |
| * in sec and nsec. |
| */ |
| static void convert_time(unsigned int *sec, unsigned int *nsec, |
| time_t sec_offset, int32_t nsec_offset) |
| { |
| sec_offset += *sec; |
| nsec_offset += *nsec; |
| if (nsec_offset >= 1000000000L) { |
| sec_offset++; |
| nsec_offset -= 1000000000L; |
| } else if (nsec_offset < 0) { |
| sec_offset--; |
| nsec_offset += 1000000000L; |
| } |
| *sec = sec_offset; |
| *nsec = nsec_offset; |
| } |
| |
| static float get_ewma_power_as_float(uint32_t data) |
| { |
| float f = 0.0f; |
| |
| /* Convert from the uint32_t log type back to float. |
| * If data cannot be assigned to float, default value will |
| * be printed as -inf to hint the problem. |
| */ |
| if (sizeof(uint32_t) == sizeof(float)) |
| memcpy(&f, &data, sizeof(float)); |
| else |
| printf("%-30s float to uint32_t\n", "MEMORY_NOT_ALIGNED"); |
| |
| /* Convert to dBFS and set to zero if it's |
| * insignificantly low. Picking the same threshold |
| * 1.0e-10f as in Chrome. |
| */ |
| return (f < 1.0e-10f) ? -INFINITY : 10.0f * log10f(f); |
| } |
| |
| static void show_alog_tag(const struct audio_thread_event_log *log, |
| unsigned int tag_idx, int32_t sec_offset, |
| int32_t nsec_offset) |
| { |
| 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; |
| time_t lt; |
| struct tm t; |
| |
| /* Skip unused log entries. */ |
| if (log->log[tag_idx].tag_sec == 0 && log->log[tag_idx].nsec == 0) |
| return; |
| |
| /* Convert from monotonic raw clock to realtime clock. */ |
| convert_time(&sec, &nsec, sec_offset, nsec_offset); |
| lt = sec; |
| localtime_r(<, &t); |
| strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", &t); |
| |
| printf("%s.%09u cras atlog ", time_str, nsec); |
| |
| /* Prepare realtime string for arguments. */ |
| switch (tag) { |
| case AUDIO_THREAD_A2DP_FLUSH: |
| case AUDIO_THREAD_READ_AUDIO_TSTAMP: |
| case AUDIO_THREAD_FILL_AUDIO_TSTAMP: |
| case AUDIO_THREAD_STREAM_RESCHEDULE: |
| case AUDIO_THREAD_STREAM_SLEEP_TIME: |
| case AUDIO_THREAD_STREAM_SLEEP_ADJUST: |
| case AUDIO_THREAD_DEV_SLEEP_TIME: |
| sec = data2; |
| nsec = data3; |
| break; |
| } |
| convert_time(&sec, &nsec, sec_offset, nsec_offset); |
| lt = sec; |
| localtime_r(<, &t); |
| strftime(time_str, 128, " %H:%M:%S", &t); |
| |
| 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 non_empty %u\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:%s.%09u\n", "READ_AUDIO_TSTAMP", |
| data1, time_str, nsec); |
| break; |
| case AUDIO_THREAD_READ_AUDIO_DONE: { |
| float f = get_ewma_power_as_float(data2); |
| printf("%-30s read_remainder:%u power:%f dBFS\n", |
| "READ_AUDIO_DONE", data1, f); |
| 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 min_cb_level:%u\n", |
| "FILL_AUDIO", data1, data2, data3); |
| break; |
| case AUDIO_THREAD_FILL_AUDIO_TSTAMP: |
| printf("%-30s dev:%u tstamp:%s.%09u\n", "FILL_AUDIO_TSTAMP", |
| data1, time_str, nsec); |
| break; |
| case AUDIO_THREAD_FILL_AUDIO_DONE: { |
| float f = get_ewma_power_as_float(data3); |
| printf("%-30s hw_level:%u total_written:%u power:%f dBFS\n", |
| "FILL_AUDIO_DONE", data1, data2, f); |
| 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: { |
| float f = get_ewma_power_as_float(data3); |
| printf("%-30s id:%x cbth:%u power:%f dBFS\n", |
| "WRITE_STREAMS_FETCH_STREAM", data1, data2, f); |
| 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; |
| break; |
| case AUDIO_THREAD_A2DP_FLUSH: |
| printf("%-30s state %u next flush time:%s.%09u\n", "A2DP_FLUSH", |
| data1, time_str, nsec); |
| break; |
| case AUDIO_THREAD_A2DP_THROTTLE_TIME: |
| printf("%-30s %u ms, queued:%u\n", "A2DP_THROTTLE_TIME", |
| data1 * 1000 + data2 / 1000000, 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_FETCH_PENDING: |
| printf("%-30s id:%x\n", "STREAM_FETCH_PENGING", data1); |
| break; |
| case AUDIO_THREAD_STREAM_RESCHEDULE: |
| printf("%-30s id:%x next_cb_ts:%s.%09u\n", "STREAM_RESCHEDULE", |
| data1, time_str, nsec); |
| break; |
| case AUDIO_THREAD_STREAM_SLEEP_TIME: |
| printf("%-30s id:%x wake:%s.%09u\n", "STREAM_SLEEP_TIME", data1, |
| time_str, nsec); |
| break; |
| case AUDIO_THREAD_STREAM_SLEEP_ADJUST: |
| printf("%-30s id:%x from:%s.%09u\n", "STREAM_SLEEP_ADJUST", |
| data1, time_str, nsec); |
| 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:%s.%09u\n", "DEV_SLEEP_TIME", data1, |
| time_str, nsec); |
| 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 revents:%u events:%u\n", "IODEV_CB", data1, |
| data2); |
| 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_UNDERRUN: |
| printf("%-30s dev:%u hw_level:%u total_written:%u\n", |
| "UNDERRUN", data1, data2, data3); |
| break; |
| case AUDIO_THREAD_SEVERE_UNDERRUN: |
| printf("%-30s dev:%u\n", "SEVERE_UNDERRUN", data1); |
| break; |
| case AUDIO_THREAD_CAPTURE_DROP_TIME: |
| printf("%-30s time:%09u.%09d\n", "CAPTURE_DROP_TIME", data1, |
| data2); |
| break; |
| case AUDIO_THREAD_DEV_DROP_FRAMES: |
| printf("%-30s dev:%u frames:%u\n", "DEV_DROP_FRAMES", data1, |
| data2); |
| break; |
| case AUDIO_THREAD_LOOPBACK_PUT: |
| printf("%-30s nframes_committed:%u\n", "LOOPBACK_PUT", data1); |
| break; |
| case AUDIO_THREAD_LOOPBACK_GET: |
| printf("%-30s nframes_requested:%u avail:%u\n", "LOOPBACK_GET", |
| data1, data2); |
| break; |
| case AUDIO_THREAD_LOOPBACK_SAMPLE_HOOK: |
| printf("%-30s frames_to_copy:%u frames_copied:%u\n", |
| "LOOPBACK_SAMPLE", data1, data2); |
| break; |
| case AUDIO_THREAD_DEV_OVERRUN: |
| printf("%-30s dev:%u hw_level:%u\n", "DEV_OVERRUN", data1, |
| data2); |
| break; |
| default: |
| printf("%-30s tag:%u\n", "UNKNOWN", tag); |
| break; |
| } |
| } |
| |
| static void print_audio_debug_info(const struct audio_debug_info *info) |
| { |
| time_t sec_offset; |
| int32_t nsec_offset; |
| int i, j; |
| |
| 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" |
| "highest_hw_level: %u\n" |
| "runtime: %u.%09u\n" |
| "longest_wake: %u.%09u\n" |
| "software_gain_scaler: %lf\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, |
| (unsigned int)info->devs[i].highest_hw_level, |
| (unsigned int)info->devs[i].runtime_sec, |
| (unsigned int)info->devs[i].runtime_nsec, |
| (unsigned int)info->devs[i].longest_wake_sec, |
| (unsigned int)info->devs[i].longest_wake_nsec, |
| info->devs[i].software_gain_scaler); |
| 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: 0x%" PRIx64 " dev: %u\n", |
| 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("client_type: %s\n", |
| cras_client_type_str(info->streams[i].client_type)); |
| printf("buffer_frames: %u\n" |
| "cb_threshold: %u\n" |
| "effects: 0x%.4x\n" |
| "frame_rate: %u\n" |
| "num_channels: %u\n" |
| "longest_fetch_sec: %u.%09u\n" |
| "num_overruns: %u\n" |
| "is_pinned: %x\n" |
| "pinned_dev_idx: %x\n" |
| "num_missed_cb: %u\n" |
| "%s: %lf\n" |
| "runtime: %u.%09u\n", |
| (unsigned int)info->streams[i].buffer_frames, |
| (unsigned int)info->streams[i].cb_threshold, |
| (unsigned int)info->streams[i].effects, |
| (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, |
| (unsigned int)info->streams[i].is_pinned, |
| (unsigned int)info->streams[i].pinned_dev_idx, |
| (unsigned int)info->streams[i].num_missed_cb, |
| (info->streams[i].direction == CRAS_STREAM_INPUT) ? |
| "gain" : |
| "volume", |
| info->streams[i].stream_volume, |
| (unsigned int)info->streams[i].runtime_sec, |
| (unsigned int)info->streams[i].runtime_nsec); |
| 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"); |
| |
| fill_time_offset(&sec_offset, &nsec_offset); |
| j = info->log.write_pos % info->log.len; |
| i = 0; |
| printf("start at %d\n", j); |
| for (; i < info->log.len; i++) { |
| show_alog_tag(&info->log, j, sec_offset, nsec_offset); |
| j++; |
| j %= info->log.len; |
| } |
| } |
| |
| static void audio_debug_info(struct cras_client *client) |
| { |
| const struct audio_debug_info *info; |
| info = cras_client_get_audio_debug_info(client); |
| if (!info) |
| return; |
| print_audio_debug_info(info); |
| |
| /* 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 void show_mainlog_tag(const struct main_thread_event_log *log, |
| unsigned int tag_idx, int32_t sec_offset, |
| int32_t nsec_offset) |
| { |
| 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; |
| time_t lt; |
| struct tm t; |
| |
| /* Skip unused log entries. */ |
| if (log->log[tag_idx].tag_sec == 0 && log->log[tag_idx].nsec == 0) |
| return; |
| |
| /* Convert from monotomic raw clock to realtime clock. */ |
| convert_time(&sec, &nsec, sec_offset, nsec_offset); |
| lt = sec; |
| localtime_r(<, &t); |
| strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", &t); |
| |
| printf("%s.%09u cras mainlog ", time_str, nsec); |
| |
| switch (tag) { |
| case MAIN_THREAD_DEV_CLOSE: |
| printf("%-30s dev %u\n", "DEV_CLOSE", data1); |
| break; |
| case MAIN_THREAD_DEV_DISABLE: |
| printf("%-30s dev %u force %u\n", "DEV_DISABLE", data1, data2); |
| break; |
| case MAIN_THREAD_DEV_INIT: |
| printf("%-30s dev %u ch %u rate %u\n", "DEV_INIT", data1, data2, |
| data3); |
| break; |
| case MAIN_THREAD_DEV_REOPEN: |
| printf("%-30s new ch %u old ch %u rate %u\n", "DEV_REOPEN", |
| data1, data2, data3); |
| break; |
| case MAIN_THREAD_ADD_ACTIVE_NODE: |
| printf("%-30s dev %u\n", "ADD_ACTIVE_NODE", data1); |
| break; |
| case MAIN_THREAD_SELECT_NODE: |
| printf("%-30s dev %u\n", "SELECT_NODE", data1); |
| break; |
| case MAIN_THREAD_ADD_TO_DEV_LIST: |
| printf("%-30s dev %u %s\n", "ADD_TO_DEV_LIST", data1, |
| (data2 == CRAS_STREAM_OUTPUT) ? "output" : "input"); |
| break; |
| case MAIN_THREAD_NODE_PLUGGED: |
| printf("%-30s dev %u %s\n", "NODE_PLUGGED", data1, |
| data2 ? "plugged" : "unplugged"); |
| break; |
| case MAIN_THREAD_INPUT_NODE_GAIN: |
| printf("%-30s dev %u gain %u\n", "INPUT_NODE_GAIN", data1, |
| data2); |
| break; |
| case MAIN_THREAD_OUTPUT_NODE_VOLUME: |
| printf("%-30s dev %u volume %u\n", "OUTPUT_NODE_VOLUME", data1, |
| data2); |
| break; |
| case MAIN_THREAD_SET_OUTPUT_USER_MUTE: |
| printf("%-30s mute %u\n", "SET_OUTPUT_USER_MUTE", data1); |
| break; |
| case MAIN_THREAD_RESUME_DEVS: |
| printf("RESUME_DEVS\n"); |
| break; |
| case MAIN_THREAD_SUSPEND_DEVS: |
| printf("SUSPEND_DEVS\n"); |
| break; |
| case MAIN_THREAD_STREAM_ADDED: |
| printf("%-30s %s stream 0x%x buffer frames %u\n", |
| "STREAM_ADDED", |
| (data2 == CRAS_STREAM_OUTPUT ? "output" : "input"), |
| data1, data3); |
| break; |
| case MAIN_THREAD_STREAM_REMOVED: |
| printf("%-30s stream 0x%x\n", "STREAM_REMOVED", data1); |
| break; |
| default: |
| printf("%-30s\n", "UNKNOWN"); |
| break; |
| } |
| } |
| |
| static void show_btlog_tag(const struct cras_bt_event_log *log, |
| unsigned int tag_idx, int32_t sec_offset, |
| int32_t nsec_offset) |
| { |
| 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; |
| time_t lt; |
| struct tm t; |
| |
| /* Skip unused log entries. */ |
| if (log->log[tag_idx].tag_sec == 0 && log->log[tag_idx].nsec == 0) |
| return; |
| |
| /* Convert from monotonic raw clock to realtime clock. */ |
| convert_time(&sec, &nsec, sec_offset, nsec_offset); |
| lt = sec; |
| localtime_r(<, &t); |
| strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", &t); |
| |
| printf("%s.%09u cras btlog ", time_str, nsec); |
| |
| switch (tag) { |
| case BT_ADAPTER_ADDED: |
| printf("%-30s\n", "ADAPTER_ADDED"); |
| break; |
| case BT_ADAPTER_REMOVED: |
| printf("%-30s\n", "ADAPTER_REMOVED"); |
| break; |
| case BT_A2DP_CONFIGURED: |
| printf("%-30s connected profiles 0x%.2x\n", "A2DP_CONFIGURED", |
| data1); |
| break; |
| case BT_A2DP_START: |
| printf("%-30s\n", "A2DP_START"); |
| break; |
| case BT_A2DP_SUSPENDED: |
| printf("%-30s\n", "A2DP_SUSPENDED"); |
| break; |
| case BT_AUDIO_GATEWAY_INIT: |
| printf("%-30s supported profiles 0x%.2x\n", |
| "AUDIO_GATEWAY_INIT", data1); |
| break; |
| case BT_AUDIO_GATEWAY_START: |
| printf("%-30s \n", "AUDIO_GATEWAY_START"); |
| break; |
| case BT_AVAILABLE_CODECS: |
| printf("%-30s codec #%u id %u\n", "AVAILABLE_CODECS", data1, |
| data2); |
| break; |
| case BT_CODEC_SELECTION: |
| printf("%-30s dir %u codec id %u\n", "CODEC_SELECTION", data1, |
| data2); |
| break; |
| case BT_DEV_CONNECTED: |
| printf("%-30s supported profiles 0x%.2x stable_id 0x%08x\n", |
| "DEV_CONNECTED", data1, data2); |
| break; |
| case BT_DEV_DISCONNECTED: |
| printf("%-30s supported profiles 0x%.2x stable_id 0x%08x\n", |
| "DEV_DISCONNECTED", data1, data2); |
| break; |
| case BT_DEV_CONN_WATCH_CB: |
| printf("%-30s %u retries left, supported profiles 0x%.2x\n", |
| "DEV_CONN_WATCH_CB", data1, data2); |
| break; |
| case BT_DEV_SUSPEND_CB: |
| printf("%-30s profiles supported %u, reason %u\n", |
| "DEV_SUSPEND_CB", data1, data2); |
| break; |
| case BT_HFP_HF_INDICATOR: |
| printf("%-30s HF read AG %s indicator\n", "HFP_HF_INDICATOR", |
| data1 ? "enabled" : "supported"); |
| break; |
| case BT_HFP_SET_SPEAKER_GAIN: |
| printf("%-30s HF set speaker gain %u\n", "HFP_SET_SPEAKER_GAIN", |
| data1); |
| break; |
| case BT_HFP_UPDATE_SPEAKER_GAIN: |
| printf("%-30s HF update speaker gain %u\n", |
| "HFP_UPDATE_SPEAKER_GAIN", data1); |
| break; |
| case BT_HFP_NEW_CONNECTION: |
| printf("%-30s\n", "HFP_NEW_CONNECTION"); |
| break; |
| case BT_HFP_REQUEST_DISCONNECT: |
| printf("%-30s\n", "HFP_REQUEST_DISCONNECT"); |
| break; |
| case BT_HFP_SUPPORTED_FEATURES: |
| printf("%-30s role %s features 0x%.4x\n", |
| "HFP_SUPPORTED_FEATURES", data1 ? "AG" : "HF", data2); |
| break; |
| case BT_HSP_NEW_CONNECTION: |
| printf("%-30s\n", "HSP_NEW_CONNECTION"); |
| break; |
| case BT_HSP_REQUEST_DISCONNECT: |
| printf("%-30s\n", "HSP_REQUEST_DISCONNECT"); |
| break; |
| case BT_NEW_AUDIO_PROFILE_AFTER_CONNECT: |
| printf("%-30s old 0x%.2x, new 0x%.2x\n", |
| "NEW_AUDIO_PROFILE_AFTER_CONNECT", data1, data2); |
| break; |
| case BT_RESET: |
| printf("%-30s\n", "RESET"); |
| break; |
| case BT_SCO_CONNECT: |
| printf("%-30s %s sk %d\n", "SCO_CONNECT", |
| data1 ? "success" : "failed", (int)data2); |
| break; |
| case BT_TRANSPORT_ACQUIRE: |
| printf("%-30s %s fd %d\n", "TRANSPORT_ACQUIRE", |
| data1 ? "success" : "failed", (int)data2); |
| break; |
| case BT_TRANSPORT_RELEASE: |
| printf("%-30s\n", "TRANSPORT_RELEASE"); |
| break; |
| case BT_TRANSPORT_SET_VOLUME: |
| printf("%-30s %d\n", "TRANSPORT_SET_VOLUME", data1); |
| break; |
| case BT_TRANSPORT_UPDATE_VOLUME: |
| printf("%-30s %d\n", "TRANSPORT_UPDATE_VOLUME", data1); |
| break; |
| default: |
| printf("%-30s\n", "UNKNOWN"); |
| break; |
| } |
| } |
| |
| static void convert_to_time_str(const struct timespec *ts, time_t sec_offset, |
| int32_t nsec_offset) |
| { |
| time_t lt = ts->tv_sec; |
| struct tm t; |
| unsigned int time_nsec; |
| |
| /* Assuming tv_nsec doesn't exceed 10^9 */ |
| time_nsec = ts->tv_nsec; |
| convert_time((unsigned int *)<, &time_nsec, sec_offset, nsec_offset); |
| localtime_r(<, &t); |
| strftime(time_str, 128, "%Y-%m-%dT%H:%M:%S", &t); |
| snprintf(time_str + strlen(time_str), 128 - strlen(time_str), ".%09u", |
| time_nsec); |
| } |
| |
| static void cras_bt_debug_info(struct cras_client *client) |
| { |
| const struct cras_bt_debug_info *info; |
| time_t sec_offset; |
| int32_t nsec_offset; |
| int i, j; |
| struct timespec ts; |
| struct packet_status_logger wbs_logger; |
| |
| info = cras_client_get_bt_debug_info(client); |
| fill_time_offset(&sec_offset, &nsec_offset); |
| j = info->bt_log.write_pos; |
| i = 0; |
| printf("BT debug log:\n"); |
| for (; i < info->bt_log.len; i++) { |
| show_btlog_tag(&info->bt_log, j, sec_offset, nsec_offset); |
| j++; |
| j %= info->bt_log.len; |
| } |
| |
| printf("-------------WBS packet loss------------\n"); |
| wbs_logger = info->wbs_logger; |
| |
| packet_status_logger_begin_ts(&wbs_logger, &ts); |
| convert_to_time_str(&ts, sec_offset, nsec_offset); |
| printf("%s [begin]\n", time_str); |
| |
| packet_status_logger_end_ts(&wbs_logger, &ts); |
| convert_to_time_str(&ts, sec_offset, nsec_offset); |
| printf("%s [end]\n", time_str); |
| |
| printf("In hex format:\n"); |
| packet_status_logger_dump_hex(&wbs_logger); |
| |
| printf("In binary format:\n"); |
| packet_status_logger_dump_binary(&wbs_logger); |
| |
| /* 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 void main_thread_debug_info(struct cras_client *client) |
| { |
| const struct main_thread_debug_info *info; |
| time_t sec_offset; |
| int32_t nsec_offset; |
| int i, j; |
| |
| info = cras_client_get_main_thread_debug_info(client); |
| fill_time_offset(&sec_offset, &nsec_offset); |
| j = info->main_log.write_pos; |
| i = 0; |
| printf("Main debug log:\n"); |
| for (; i < info->main_log.len; i++) { |
| show_mainlog_tag(&info->main_log, j, sec_offset, nsec_offset); |
| j++; |
| j %= info->main_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 void print_cras_audio_thread_snapshot( |
| const struct cras_audio_thread_snapshot *snapshot) |
| { |
| printf("-------------snapshot------------\n"); |
| printf("Event time: %" PRId64 ".%ld\n", |
| (int64_t)snapshot->timestamp.tv_sec, |
| snapshot->timestamp.tv_nsec); |
| |
| printf("Event type: "); |
| switch (snapshot->event_type) { |
| case AUDIO_THREAD_EVENT_A2DP_THROTTLE: |
| printf("a2dp throttle\n"); |
| break; |
| case AUDIO_THREAD_EVENT_BUSYLOOP: |
| printf("busyloop\n"); |
| break; |
| case AUDIO_THREAD_EVENT_UNDERRUN: |
| printf("underrun\n"); |
| break; |
| case AUDIO_THREAD_EVENT_SEVERE_UNDERRUN: |
| printf("severe underrun\n"); |
| break; |
| case AUDIO_THREAD_EVENT_DROP_SAMPLES: |
| printf("drop samples\n"); |
| break; |
| case AUDIO_THREAD_EVENT_DEV_OVERRUN: |
| printf("device overrun\n"); |
| break; |
| case AUDIO_THREAD_EVENT_DEBUG: |
| printf("debug\n"); |
| break; |
| default: |
| printf("no such type\n"); |
| } |
| print_audio_debug_info(&snapshot->audio_debug_info); |
| } |
| |
| static void audio_thread_snapshots(struct cras_client *client) |
| { |
| const struct cras_audio_thread_snapshot_buffer *snapshot_buffer; |
| uint32_t i; |
| int j; |
| int count = 0; |
| |
| snapshot_buffer = cras_client_get_audio_thread_snapshot_buffer(client); |
| i = snapshot_buffer->pos; |
| for (j = 0; j < CRAS_MAX_AUDIO_THREAD_SNAPSHOTS; j++) { |
| if (snapshot_buffer->snapshots[i].timestamp.tv_sec || |
| snapshot_buffer->snapshots[i].timestamp.tv_nsec) { |
| print_cras_audio_thread_snapshot( |
| &snapshot_buffer->snapshots[i]); |
| count++; |
| } |
| i++; |
| i %= CRAS_MAX_AUDIO_THREAD_SNAPSHOTS; |
| } |
| printf("There are %d, snapshots.\n", count); |
| |
| /* 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 void run_aecdump(struct cras_client *client, uint64_t stream_id, |
| int start) |
| { |
| int aecdump_fd; |
| if (start) { |
| aecdump_fd = |
| open(aecdump_file, O_CREAT | O_RDWR | O_TRUNC, 0666); |
| if (aecdump_fd == -1) { |
| printf("Fail to open file %s", aecdump_file); |
| return; |
| } |
| |
| printf("Dumping AEC info to %s, stream %" PRId64 ", fd %d\n", |
| aecdump_file, stream_id, aecdump_fd); |
| cras_client_set_aec_dump(client, stream_id, 1, aecdump_fd); |
| } else { |
| cras_client_set_aec_dump(client, stream_id, 0, -1); |
| printf("Close AEC dump file %s\n", aecdump_file); |
| } |
| } |
| |
| 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, |
| snd_pcm_format_t format, size_t num_channels, |
| uint32_t flags, int is_loopback, int is_post_dsp) |
| { |
| 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; |
| 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) |
| 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(format, 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_stream_params_set_client_type(params, |
| CRAS_CLIENT_TYPE_TEST); |
| |
| if (effect_aec) |
| cras_client_stream_params_enable_aec(params); |
| if (effect_ns) |
| cras_client_stream_params_enable_ns(params); |
| if (effect_agc) |
| cras_client_stream_params_enable_agc(params); |
| if (effect_vad) |
| cras_client_stream_params_enable_vad(params); |
| |
| cras_client_run_thread(client); |
| if (is_loopback) { |
| enum CRAS_NODE_TYPE type = |
| (is_post_dsp ? CRAS_NODE_TYPE_POST_DSP : |
| CRAS_NODE_TYPE_POST_MIX_PRE_DSP); |
| |
| cras_client_connected_wait(client); |
| pin_device_id = cras_client_get_first_dev_type_idx( |
| client, type, 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 '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: %s\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_muted(client) ? |
| "Muted" : |
| "Not muted"); |
| 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, snd_pcm_format_t format, |
| size_t num_channels, uint32_t flags, int is_loopback, |
| int is_post_dsp) |
| { |
| 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, format, num_channels, flags, |
| is_loopback, is_post_dsp); |
| |
| 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, snd_pcm_format_t format, |
| 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, format, num_channels, 0, 0, 0); |
| |
| close(fd); |
| 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 show_audio_thread_snapshots(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_thread_snapshots(client, |
| audio_thread_snapshots); |
| |
| 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 show_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 show_cras_bt_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_bt_debug_info(client, cras_bt_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 show_main_thread_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_main_thread_debug_info(client, |
| main_thread_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 un-mute 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 void show_atlog(time_t sec_offset, int32_t nsec_offset, |
| struct audio_thread_event_log *log, int len, |
| uint64_t missing) |
| { |
| int i; |
| printf("Audio Thread Event Log:\n"); |
| |
| if (missing) |
| printf("%" PRIu64 " logs are missing.\n", missing); |
| |
| for (i = 0; i < len; ++i) { |
| show_alog_tag(log, i, sec_offset, nsec_offset); |
| } |
| } |
| |
| static void unlock_main_thread(struct cras_client *client) |
| { |
| pthread_mutex_lock(&done_mutex); |
| pthread_cond_signal(&done_cond); |
| pthread_mutex_unlock(&done_mutex); |
| } |
| |
| static void cras_show_continuous_atlog(struct cras_client *client) |
| { |
| struct audio_thread_event_log log; |
| struct timespec wait_time; |
| static time_t sec_offset; |
| static int32_t nsec_offset; |
| static uint64_t atlog_read_idx = 0, missing; |
| int len, rc; |
| |
| cras_client_run_thread(client); |
| cras_client_connected_wait(client); /* To synchronize data. */ |
| cras_client_get_atlog_access(client, unlock_main_thread); |
| |
| clock_gettime(CLOCK_REALTIME, &wait_time); |
| wait_time.tv_sec += 2; |
| |
| pthread_mutex_lock(&done_mutex); |
| rc = pthread_cond_timedwait(&done_cond, &done_mutex, &wait_time); |
| pthread_mutex_unlock(&done_mutex); |
| |
| if (rc) |
| goto fail; |
| |
| fill_time_offset(&sec_offset, &nsec_offset); |
| |
| /* Set stdout buffer to line buffered mode. */ |
| setlinebuf(stdout); |
| |
| while (1) { |
| len = cras_client_read_atlog(client, &atlog_read_idx, &missing, |
| &log); |
| |
| if (len < 0) |
| break; |
| if (len > 0) |
| show_atlog(sec_offset, nsec_offset, &log, len, missing); |
| nanosleep(&follow_atlog_sleep_ts, NULL); |
| } |
| fail: |
| printf("Failed to get audio thread log.\n"); |
| } |
| |
| // clang-format off |
| 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'}, |
| {"num_channels", required_argument, 0, 'c'}, |
| {"duration_seconds", required_argument, 0, 'd'}, |
| {"dump_events", no_argument, 0, 'e'}, |
| {"format", required_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'}, |
| {"dump_dsp", no_argument, 0, 'l'}, |
| {"dump_audio_thread", no_argument, 0, 'm'}, |
| {"syslog_mask", required_argument, 0, 'n'}, |
| {"channel_layout", required_argument, 0, 'o'}, |
| {"get_aec_group_id", no_argument, 0, 'p'}, |
| {"user_mute", required_argument, 0, 'q'}, |
| {"rate", required_argument, 0, 'r'}, |
| {"reload_dsp", no_argument, 0, 's'}, |
| {"add_active_output", required_argument, 0, 't'}, |
| {"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", required_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, '>'}, |
| {"post_dsp", required_argument, 0, 'A'}, |
| {"stream_id", required_argument, 0, 'B'}, |
| {"capture_file", required_argument, 0, 'C'}, |
| {"reload_aec_config", no_argument, 0, 'D'}, |
| {"effects", required_argument, 0, 'E'}, |
| {"get_aec_supported", no_argument, 0, 'F'}, |
| {"aecdump", required_argument, 0, 'G'}, |
| {"dump_bt", no_argument, 0, 'H'}, |
| {"set_wbs_enabled", required_argument, 0, 'I'}, |
| {"follow_atlog", no_argument, 0, 'J'}, |
| {"connection_type", required_argument, 0, 'K'}, |
| {"loopback_file", required_argument, 0, 'L'}, |
| {"mute_loop_test", required_argument, 0, 'M'}, |
| {"dump_main", no_argument, 0, 'N'}, |
| {"playback_file", required_argument, 0, 'P'}, |
| {"stream_type", required_argument, 0, 'T'}, |
| {0, 0, 0, 0} |
| }; |
| // clang-format on |
| |
| static void show_usage() |
| { |
| int i; |
| |
| 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 capture 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("--connection_type <connection_type> - " |
| "Set cras_client connection_type (default to 0).\n" |
| " " |
| "Argument: 0 - For control client.\n" |
| " " |
| " 1 - For playback client.\n" |
| " " |
| " 2 - For capture client.\n" |
| " " |
| " 3 - For legacy client in vms.\n" |
| " " |
| " 4 - For unified client in vms.\n"); |
| printf("--dump_audio_thread - " |
| "Dumps audio thread info.\n"); |
| printf("--dump_bt - " |
| "Dumps debug info for bt audio\n"); |
| printf("--dump_main - " |
| "Dumps debug info from main thread\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("--follow_atlog - " |
| "Continuously dumps audio thread event log.\n"); |
| printf("--format <name> - " |
| "The sample format. Either "); |
| for (i = 0; supported_formats[i].name; ++i) |
| printf("%s ", supported_formats[i].name); |
| printf("(default to S16_LE).\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 <name> - " |
| "Listen and capture hotword stream if supported\n"); |
| printf("--loopback_file <name> - " |
| "Name of file to record from loopback device.\n"); |
| printf("--mute <0|1> - " |
| "Set system mute state.\n"); |
| printf("--mute_loop_test <0|1> - " |
| "Continuously loop mute/un-mute.\n" |
| " " |
| "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("--post_dsp <0|1> - " |
| "Use this flag with --loopback_file. The default value is 0.\n" |
| " " |
| "Argument: 0 - Record from post-mix, pre-DSP loopback device.\n" |
| " " |
| " 1 - Record from post-DSP loopback device.\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 un-swap (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"); |
| } |
| |
| static int cras_client_create_and_connect(struct cras_client **client, |
| enum CRAS_CONNECTION_TYPE conn_type) |
| { |
| int rc; |
| |
| rc = cras_client_create_with_type(client, conn_type); |
| 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"); |
| cras_client_destroy(*client); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| 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; |
| int post_dsp = 0; |
| enum CRAS_STREAM_TYPE stream_type = CRAS_STREAM_TYPE_DEFAULT; |
| int rc = 0; |
| uint32_t stream_flags = 0; |
| cras_stream_id_t stream_id = 0; |
| snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; |
| enum CRAS_CONNECTION_TYPE conn_type = CRAS_CONTROL; |
| enum CRAS_CONNECTION_TYPE new_conn_type; |
| |
| option_index = 0; |
| openlog("cras_test_client", LOG_PERROR, LOG_USER); |
| setlogmask(LOG_UPTO(LOG_INFO)); |
| |
| rc = cras_client_create_and_connect(&client, conn_type); |
| if (rc) { |
| return rc; |
| } |
| |
| if (argc == 1) { |
| /* Nothing specified, default to dump_server_info. */ |
| print_server_info(client); |
| goto destroy_exit; |
| } |
| |
| while (1) { |
| c = getopt_long(argc, argv, "o:s:P:C:r:c:f:h", long_options, |
| &option_index); |
| if (c == -1) |
| break; |
| switch (c) { |
| case 'y': |
| case 'a': { |
| cras_node_id_t id; |
| rc = parse_node_id(optarg, &id); |
| if (rc) { |
| show_usage(); |
| return rc; |
| } |
| |
| enum CRAS_STREAM_DIRECTION direction = |
| (c == 'y') ? CRAS_STREAM_OUTPUT : |
| CRAS_STREAM_INPUT; |
| cras_client_select_node(client, direction, id); |
| break; |
| } |
| case 'b': |
| block_size = atoi(optarg); |
| break; |
| case 'c': |
| num_channels = atoi(optarg); |
| break; |
| case 'd': |
| duration_seconds = atof(optarg); |
| break; |
| case 'e': |
| show_audio_thread_snapshots(client); |
| break; |
| case 'f': { |
| int i; |
| |
| for (i = 0; supported_formats[i].name; ++i) { |
| if (strcasecmp(optarg, |
| supported_formats[i].name) == |
| 0) { |
| format = supported_formats[i].format; |
| break; |
| } |
| } |
| |
| if (!supported_formats[i].name) { |
| printf("Unsupported format: %s\n", optarg); |
| return -EINVAL; |
| } |
| break; |
| } |
| case 'h': |
| show_usage(); |
| break; |
| case 'i': |
| print_server_info(client); |
| break; |
| case 'j': |
| check_output_plugged(client, optarg); |
| break; |
| case 'k': |
| case 't': |
| case '1': |
| case '2': { |
| cras_node_id_t id; |
| rc = parse_node_id(optarg, &id); |
| if (rc) { |
| show_usage(); |
| return rc; |
| } |
| |
| enum CRAS_STREAM_DIRECTION dir; |
| 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 'l': |
| cras_client_dump_dsp_info(client); |
| break; |
| case 'm': |
| show_audio_debug_info(client); |
| break; |
| case 'n': { |
| int log_level = atoi(optarg); |
| |
| setlogmask(LOG_UPTO(log_level)); |
| break; |
| } |
| case 'o': |
| channel_layout = optarg; |
| break; |
| case 'p': |
| printf("AEC group ID %d\n", |
| cras_client_get_aec_group_id(client)); |
| 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 'r': |
| rate = atoi(optarg); |
| break; |
| case 's': |
| cras_client_reload_dsp(client); |
| 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 '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 ':': |
| case 'w': { |
| cras_node_id_t id; |
| int value; |
| rc = parse_node_id_with_value(optarg, &id, &value); |
| if (rc) { |
| show_usage(); |
| return rc; |
| } |
| |
| if (c == 'w') |
| cras_client_set_node_volume(client, id, value); |
| else |
| cras_client_set_node_capture_gain(client, id, |
| value); |
| break; |
| } |
| case 'x': { |
| cras_node_id_t id; |
| int value; |
| rc = parse_node_id_with_value(optarg, &id, &value); |
| if (rc) { |
| show_usage(); |
| return rc; |
| } |
| |
| enum ionode_attr attr = IONODE_ATTR_PLUGGED; |
| cras_client_set_node_attr(client, id, attr, value); |
| break; |
| } |
| case 'z': |
| pause_in_playback_reply = atoi(optarg); |
| 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 '3': { |
| cras_node_id_t id; |
| int value; |
| rc = parse_node_id_with_value(optarg, &id, &value); |
| if (rc) { |
| show_usage(); |
| return rc; |
| } |
| |
| 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': { |
| const char *s; |
| int dev_index; |
| |
| s = strtok(optarg, ":"); |
| if (!s) { |
| show_usage(); |
| return -EINVAL; |
| } |
| dev_index = atoi(s); |
| |
| const char *file_name = strtok(NULL, ":"); |
| if (!file_name) { |
| show_usage(); |
| return -EINVAL; |
| } |
| cras_client_test_iodev_command( |
| client, dev_index, |
| TEST_IODEV_CMD_HOTWORD_TRIGGER, |
| strlen(file_name) + 1, (uint8_t *)file_name); |
| break; |
| } |
| case '7': { |
| stream_flags = HOTWORD_STREAM; |
| capture_file = optarg; |
| 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((size_t)nch * (size_t)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 == ';') { |
| //TODO: c never == ';' |
| 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 'A': |
| post_dsp = atoi(optarg); |
| break; |
| case 'B': |
| stream_id = atoi(optarg); |
| break; |
| case 'C': |
| capture_file = optarg; |
| break; |
| case 'D': |
| cras_client_reload_aec_config(client); |
| break; |
| case 'E': { |
| char *s; |
| |
| s = strtok(optarg, ","); |
| while (s) { |
| if (strcmp("aec", s) == 0) |
| effect_aec = 1; |
| else if (strcmp("ns", s) == 0) |
| effect_ns = 1; |
| else if (strcmp("agc", s) == 0) |
| effect_agc = 1; |
| else if (strcmp("vad", s) == 0) |
| effect_vad = 1; |
| else |
| printf("Unknown effect %s\n", s); |
| s = strtok(NULL, ","); |
| } |
| break; |
| } |
| case 'F': |
| printf("AEC supported %d\n", |
| !!cras_client_get_aec_supported(client)); |
| break; |
| case 'G': |
| aecdump_file = optarg; |
| break; |
| case 'H': |
| show_cras_bt_debug_info(client); |
| break; |
| case 'I': |
| cras_client_set_bt_wbs_enabled(client, atoi(optarg)); |
| break; |
| case 'J': |
| cras_show_continuous_atlog(client); |
| break; |
| case 'K': |
| new_conn_type = atoi(optarg); |
| if (cras_validate_connection_type(new_conn_type)) { |
| if (new_conn_type != conn_type) { |
| cras_client_destroy(client); |
| client = NULL; |
| rc = cras_client_create_and_connect( |
| &client, new_conn_type); |
| if (rc) { |
| fprintf(stderr, |
| "Couldn't connect to " |
| "server.\n"); |
| return rc; |
| } |
| conn_type = new_conn_type; |
| } |
| } else { |
| printf("Input connection type is not " |
| "supported.\n"); |
| } |
| break; |
| case 'L': |
| loopback_file = optarg; |
| break; |
| case 'M': |
| mute_loop_test(client, atoi(optarg)); |
| break; |
| case 'N': |
| show_main_thread_debug_info(client); |
| break; |
| case 'P': |
| playback_file = optarg; |
| break; |
| case 'T': |
| stream_type = atoi(optarg); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if (optind < argc) { |
| printf("Warning: un-welcome arguments: "); |
| while (optind < argc) |
| printf("%s ", argv[optind++]); |
| printf("\n"); |
| rc = 1; |
| goto destroy_exit; |
| } |
| |
| 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, |
| format, num_channels, |
| stream_flags, 0, 0); |
| else |
| rc = run_capture(client, capture_file, block_size, |
| stream_type, rate, format, |
| num_channels, stream_flags, 0, 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, |
| format, num_channels, |
| stream_flags, 0, 0); |
| else |
| rc = run_playback(client, playback_file, block_size, |
| stream_type, rate, format, |
| num_channels); |
| } else if (loopback_file != NULL) { |
| rc = run_capture(client, loopback_file, block_size, stream_type, |
| rate, format, num_channels, stream_flags, 1, |
| post_dsp); |
| } else if (aecdump_file != NULL) { |
| run_aecdump(client, stream_id, 1); |
| sleep(duration_seconds); |
| run_aecdump(client, stream_id, 0); |
| } |
| |
| destroy_exit: |
| cras_client_destroy(client); |
| return rc; |
| } |